kopia lustrzana https://github.com/FacilMap/facilmap
Restructure server code
rodzic
6d191703f2
commit
3369ace90d
|
@ -1,631 +0,0 @@
|
||||||
var backend = require("./databaseBackendSequelize");
|
|
||||||
var listeners = require("./listeners");
|
|
||||||
var routing = require("./routing");
|
|
||||||
var utils = require("./utils");
|
|
||||||
var underscore = require("underscore");
|
|
||||||
var stream = require("stream");
|
|
||||||
var Promise = require("promise");
|
|
||||||
|
|
||||||
var DEFAULT_TYPES = [
|
|
||||||
{ name: "Marker", type: "marker", fields: [ { name: "Description", type: "textarea" } ] },
|
|
||||||
{ name: "Line", type: "line", fields: [ { name: "Description", type: "textarea" } ] }
|
|
||||||
];
|
|
||||||
|
|
||||||
function padIdExists(padId) {
|
|
||||||
return backend.padIdExists(padId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPadData(padId) {
|
|
||||||
return backend.getPadDataByWriteId(padId).then(function(data) {
|
|
||||||
if(data != null)
|
|
||||||
return utils.extend(JSON.parse(JSON.stringify(data)), { writable: true });
|
|
||||||
|
|
||||||
return backend.getPadData(padId).then(function(data) {
|
|
||||||
if(data != null)
|
|
||||||
return utils.extend(JSON.parse(JSON.stringify(data)), { writable: false, writeId: null });
|
|
||||||
|
|
||||||
throw "This pad does not exist.";
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createPad(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!data.id || data.id.length == 0)
|
|
||||||
throw "Invalid read-only ID";
|
|
||||||
if(!data.writeId || data.writeId.length == 0)
|
|
||||||
throw "Invalid write-only ID";
|
|
||||||
if(data.id == data.writeId)
|
|
||||||
throw "Read-only and write-only ID cannot be the same.";
|
|
||||||
|
|
||||||
return Promise.all([
|
|
||||||
padIdExists(data.id).then(function(exists) {
|
|
||||||
if(exists)
|
|
||||||
throw "ID '" + data.id + "' is already taken.";
|
|
||||||
}),
|
|
||||||
padIdExists(data.writeId).then(function(exists) {
|
|
||||||
if(exists)
|
|
||||||
throw "ID '" + data.writeId + "' is already taken.";
|
|
||||||
})
|
|
||||||
])
|
|
||||||
}).then(function() {
|
|
||||||
return backend.createPad(data);
|
|
||||||
}).then(function(newData) {
|
|
||||||
data = newData;
|
|
||||||
|
|
||||||
return Promise.all(DEFAULT_TYPES.map(function(it) {
|
|
||||||
return backend.createType(data.id, it);
|
|
||||||
}));
|
|
||||||
}).then(function() {
|
|
||||||
return utils.extend(JSON.parse(JSON.stringify(data)), { writable: true });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatePadData(padId, data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(data.id != null && data.id != padId && data.id.length == 0)
|
|
||||||
throw "Invalid read-only ID";
|
|
||||||
|
|
||||||
var existsPromises = [ ];
|
|
||||||
|
|
||||||
if(data.id != null && data.id != padId) {
|
|
||||||
existsPromises.push(padIdExists(data.id).then(function(exists) {
|
|
||||||
if(exists)
|
|
||||||
throw "ID '" + data.id + "' is already taken.";
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(data.writeId != null) {
|
|
||||||
existsPromises.push(backend.getPadData(padId).then(function(padData) {
|
|
||||||
if(data.writeId != padData.writeId) {
|
|
||||||
if(data.writeId.length == 0)
|
|
||||||
throw "Invalid write-only ID";
|
|
||||||
if(data.writeId == (data.id != null ? data.id : padId))
|
|
||||||
throw "Read-only and write-only ID cannot be the same.";
|
|
||||||
|
|
||||||
return padIdExists(data.writeId).then(function(exists) {
|
|
||||||
if(exists)
|
|
||||||
throw "ID '" + data.writeId + "' is already taken.";
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(existsPromises);
|
|
||||||
}).then(function() {
|
|
||||||
return backend.updatePadData(padId, data);
|
|
||||||
}).then(function(newData) {
|
|
||||||
listeners.notifyPadListeners(padId, "padData", function(listener) {
|
|
||||||
var dataClone = JSON.parse(JSON.stringify(newData));
|
|
||||||
if(!listener.writable)
|
|
||||||
dataClone.writeId = null;
|
|
||||||
|
|
||||||
return dataClone;
|
|
||||||
});
|
|
||||||
|
|
||||||
if(data.id != null && data.id != padId)
|
|
||||||
listeners.changePadId(padId, data.id);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getViews(padId) {
|
|
||||||
return backend.getViews(padId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createView(padId, data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(data.name == null || data.name.trim().length == 0)
|
|
||||||
throw "No name provided.";
|
|
||||||
|
|
||||||
return backend.createView(padId, data);
|
|
||||||
}).then(function(data) {
|
|
||||||
listeners.notifyPadListeners(data.padId, "view", data);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateView(viewId, data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(data.name == null || data.name.trim().length == 0)
|
|
||||||
throw "No name provided.";
|
|
||||||
|
|
||||||
return backend.updateView(viewId, data);
|
|
||||||
}).then(function(data) {
|
|
||||||
listeners.notifyPadListeners(data.padId, "view", data);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteView(viewId) {
|
|
||||||
return backend.deleteView(viewId).then(function(data) {
|
|
||||||
listeners.notifyPadListeners(data.padId, "deleteView", { id: data.id });
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTypes(padId) {
|
|
||||||
return backend.getTypes(padId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createType(padId, data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(data.name == null || data.name.trim().length == 0)
|
|
||||||
throw "No name provided.";
|
|
||||||
|
|
||||||
return backend.createType(padId, data);
|
|
||||||
}).then(function(data) {
|
|
||||||
listeners.notifyPadListeners(data.padId, "type", data);
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateType(typeId, data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(data.name == null || data.name.trim().length == 0)
|
|
||||||
throw "No name provided.";
|
|
||||||
|
|
||||||
return backend.updateType(typeId, data);
|
|
||||||
}).then(function(data) {
|
|
||||||
listeners.notifyPadListeners(data.padId, "type", data);
|
|
||||||
|
|
||||||
return _updateObjectStyles(data.type == "line" ? backend.getPadLinesByType(data.padId, typeId) : backend.getPadMarkersByType(data.padId, typeId), data.type == "line").then(function() {
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _optionsToObj(options, idx) {
|
|
||||||
var ret = { };
|
|
||||||
if(options) {
|
|
||||||
for(var i=0; i<options.length; i++) {
|
|
||||||
ret[options[i].key] = options[i][idx];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteType(typeId) {
|
|
||||||
return backend.isTypeUsed(typeId).then(function(isUsed) {
|
|
||||||
if(isUsed)
|
|
||||||
throw "This type is in use.";
|
|
||||||
|
|
||||||
return backend.deleteType(typeId);
|
|
||||||
}).then(function(data) {
|
|
||||||
listeners.notifyPadListeners(data.padId, "deleteType", { id: data.id });
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPadMarkers(padId, bbox) {
|
|
||||||
return backend.getPadMarkers(padId, bbox);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMarker(padId, data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
return backend.getType(data.typeId);
|
|
||||||
}).then(function(type) {
|
|
||||||
if(type.defaultColour)
|
|
||||||
data.colour = type.defaultColour;
|
|
||||||
if(type.defaultSize)
|
|
||||||
data.size = type.defaultSize;
|
|
||||||
if(type.defaultSymbol)
|
|
||||||
data.symbol = type.defaultSymbol;
|
|
||||||
|
|
||||||
return backend.createMarker(padId, data)
|
|
||||||
}).then(function(data) {
|
|
||||||
listeners.notifyPadListeners(padId, "marker", _getMarkerDataFunc(data));
|
|
||||||
|
|
||||||
return _updateObjectStyles(data, false).then(function() {
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMarker(markerId, data) {
|
|
||||||
return _updateMarker(markerId, data).then(function(data) {
|
|
||||||
return _updateObjectStyles(data, false).then(function() {
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _updateMarker(markerId, data) {
|
|
||||||
return backend.updateMarker(markerId, data).then(function(data) {
|
|
||||||
listeners.notifyPadListeners(data.padId, "marker", _getMarkerDataFunc(data));
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteMarker(markerId) {
|
|
||||||
return backend.deleteMarker(markerId).then(function(data) {
|
|
||||||
listeners.notifyPadListeners(data.padId, "deleteMarker", { id: data.id });
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _updateObjectStyles(objectStream, isLine) {
|
|
||||||
if(!(objectStream instanceof stream.Readable))
|
|
||||||
objectStream = new utils.ArrayStream([ objectStream ]);
|
|
||||||
|
|
||||||
var types = { };
|
|
||||||
return utils.streamEachPromise(objectStream, function(object) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!types[object.typeId]) {
|
|
||||||
return backend.getType(object.typeId).then(function(type) {
|
|
||||||
if(type == null)
|
|
||||||
throw "Type "+object.typeId+" does not exist.";
|
|
||||||
|
|
||||||
return types[object.typeId] = type;
|
|
||||||
});
|
|
||||||
} else
|
|
||||||
return types[object.typeId];
|
|
||||||
}).then(function(type) {
|
|
||||||
var update = { };
|
|
||||||
|
|
||||||
if(type.colourFixed && object.colour != type.defaultColour)
|
|
||||||
update.colour = type.defaultColour;
|
|
||||||
if(!isLine && type.sizeFixed && object.size != type.defaultSize)
|
|
||||||
update.size = type.defaultSize;
|
|
||||||
if(!isLine && type.symbolFixed && object.symbol != type.defaultSymbol)
|
|
||||||
update.symbol = type.defaultSymbol;
|
|
||||||
if(isLine && type.widthFixed && object.width != type.defaultWidth)
|
|
||||||
update.width = type.defaultWidth;
|
|
||||||
if(isLine && type.modeFixed && object.mode != "track" && object.mode != type.defaultMode)
|
|
||||||
update.mode = type.defaultMode;
|
|
||||||
|
|
||||||
types[object.typeId].fields.forEach(function(field) {
|
|
||||||
if(field.type == "dropdown" && (field.controlColour || (!isLine && field.controlSize) || (!isLine && field.controlSymbol) || (isLine && field.controlWidth))) {
|
|
||||||
var _find = function(value) {
|
|
||||||
for(var j=0; j<(field.options || []).length; j++) {
|
|
||||||
if(field.options[j].key == value)
|
|
||||||
return field.options[j];
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
var option = _find(object.data[field.name]) || _find(field.default) || field.options[0];
|
|
||||||
|
|
||||||
if(option != null) {
|
|
||||||
if(field.controlColour && object.colour != option.colour)
|
|
||||||
update.colour = option.colour;
|
|
||||||
if(!isLine && field.controlSize && object.size != option.size)
|
|
||||||
update.size = option.size;
|
|
||||||
if(!isLine && field.controlSymbol && object.symbol != option.symbol)
|
|
||||||
update.symbol = option.symbol;
|
|
||||||
if(isLine && field.controlWidth && object.width != option.width)
|
|
||||||
update.width = option.width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var ret = [ ];
|
|
||||||
|
|
||||||
if(Object.keys(update).length > 0) {
|
|
||||||
utils.extend(object, update);
|
|
||||||
|
|
||||||
if(object.id) // Objects from getLineTemplate() do not have an ID
|
|
||||||
ret.push((isLine ? _updateLine : _updateMarker)(object.id, update));
|
|
||||||
|
|
||||||
if(object.id && isLine && "mode" in update) {
|
|
||||||
ret.push(_calculateRouting(object).then(function(trackPoints) {
|
|
||||||
return _setLinePoints(object.padId, object.id, trackPoints);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(ret);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPadLines(padId) {
|
|
||||||
return backend.getPadLines(padId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPadLinesWithPoints(padId, bboxWithZoom) {
|
|
||||||
return utils.filterStreamPromise(backend.getPadLines(padId), function(data) {
|
|
||||||
return _getLinePoints(data.id, bboxWithZoom).then(function(trackPoints) {
|
|
||||||
data.trackPoints = trackPoints;
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLineTemplate(data) {
|
|
||||||
return utils.promiseAllObject({
|
|
||||||
lineTemplate: backend.getLineTemplate(data),
|
|
||||||
type: backend.getType(data.typeId)
|
|
||||||
}).then(function(res) {
|
|
||||||
var line = res.lineTemplate;
|
|
||||||
|
|
||||||
if(res.type.defaultColour)
|
|
||||||
line.colour = res.type.defaultColour;
|
|
||||||
if(res.type.defaultWidth)
|
|
||||||
line.width = res.type.defaultWidth;
|
|
||||||
if(res.type.defaultMode)
|
|
||||||
res.mode = res.type.defaultMode;
|
|
||||||
|
|
||||||
return _updateObjectStyles(line, true).then(function() {
|
|
||||||
return line;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function createLine(padId, data) {
|
|
||||||
var defaultValsP = backend.getType(data.typeId).then(function(type) {
|
|
||||||
if(type.defaultColour && !("colour" in data))
|
|
||||||
data.colour = type.defaultColour;
|
|
||||||
if(type.defaultWidth && !("width" in data))
|
|
||||||
data.width = type.defaultWidth;
|
|
||||||
if(type.defaultMode && !("mode" in data))
|
|
||||||
data.mode = type.defaultMode;
|
|
||||||
});
|
|
||||||
|
|
||||||
var calculateRoutingP = defaultValsP.then(function() {
|
|
||||||
return _calculateRouting(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
var createLineP = calculateRoutingP.then(function() {
|
|
||||||
return _createLine(padId, data);
|
|
||||||
});
|
|
||||||
|
|
||||||
var setLinePointsP = Promise.all([ calculateRoutingP, createLineP ]).then(function(res) {
|
|
||||||
return _setLinePoints(padId, res[1].id, res[0]);
|
|
||||||
});
|
|
||||||
|
|
||||||
var updateLineStyleP = createLineP.then(function(lineData) {
|
|
||||||
return _updateObjectStyles(lineData, true);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.all([ defaultValsP, calculateRoutingP, createLineP, setLinePointsP, updateLineStyleP ]).then(function(res) {
|
|
||||||
return res[2];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLine(lineId, data) {
|
|
||||||
var originalLineP = backend.getLine(lineId);
|
|
||||||
|
|
||||||
var calculateRoutingP = originalLineP.then(function(originalLine) {
|
|
||||||
if(data.routePoints == null)
|
|
||||||
data.routePoints = originalLine.routePoints;
|
|
||||||
|
|
||||||
if(data.mode == null)
|
|
||||||
data.mode = originalLine.mode || "";
|
|
||||||
|
|
||||||
if((data.mode == "track" && data.trackPoints) || !underscore.isEqual(data.routePoints, originalLine.routePoints) || data.mode != originalLine.mode)
|
|
||||||
return _calculateRouting(data); // Also sets data.distance and data.time
|
|
||||||
});
|
|
||||||
|
|
||||||
var updateLineP = calculateRoutingP.then(function() {
|
|
||||||
return _updateLine(lineId, data);
|
|
||||||
});
|
|
||||||
|
|
||||||
var updateLineStyleP = updateLineP.then(function(newLine) {
|
|
||||||
return _updateObjectStyles(newLine, true); // Modifies res.updateLine
|
|
||||||
});
|
|
||||||
|
|
||||||
var setLinePointsP = Promise.all([ originalLineP, calculateRoutingP ]).then(function(res) {
|
|
||||||
if(res[1])
|
|
||||||
return _setLinePoints(res[0].padId, lineId, res[1]);
|
|
||||||
});
|
|
||||||
|
|
||||||
return Promise.all([ originalLineP, calculateRoutingP, updateLineP, updateLineStyleP, setLinePointsP ]).then(function(res) {
|
|
||||||
return res[2];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _createLine(padId, data) {
|
|
||||||
var dataCopy = utils.extend({ }, data);
|
|
||||||
delete dataCopy.trackPoints; // They came if mode is track
|
|
||||||
|
|
||||||
return backend.createLine(padId, dataCopy).then(function(newData) {
|
|
||||||
listeners.notifyPadListeners(newData.padId, "line", newData);
|
|
||||||
|
|
||||||
return newData;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _updateLine(lineId, data) {
|
|
||||||
var dataCopy = utils.extend({ }, data);
|
|
||||||
delete dataCopy.trackPoints; // They came if mode is track
|
|
||||||
|
|
||||||
return backend.updateLine(lineId, dataCopy).then(function(newData) {
|
|
||||||
listeners.notifyPadListeners(newData.padId, "line", newData);
|
|
||||||
|
|
||||||
return newData;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _setLinePoints(padId, lineId, trackPoints) {
|
|
||||||
return backend.setLinePoints(lineId, trackPoints).then(function() {
|
|
||||||
listeners.notifyPadListeners(padId, "linePoints", function(listener) {
|
|
||||||
return { reset: true, id: lineId, trackPoints : (listener && listener.bbox ? routing.prepareForBoundingBox(trackPoints, listener.bbox) : [ ]) };
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteLine(lineId) {
|
|
||||||
return backend.deleteLine(lineId).then(function(data) {
|
|
||||||
return backend.setLinePoints(lineId, [ ]).then(function() {
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}).then(function(data) {
|
|
||||||
listeners.notifyPadListeners(data.padId, "deleteLine", { id: data.id });
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLinePoints(padId, bboxWithZoom) {
|
|
||||||
return utils.filterStreamPromise(backend.getPadLines(padId, "id"), function(data) {
|
|
||||||
return _getLinePoints(data.id, bboxWithZoom).then(function(trackPoints) {
|
|
||||||
if(trackPoints.length >= 2)
|
|
||||||
return { id: data.id, trackPoints: trackPoints };
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/*function copyPad(fromPadId, toPadId, callback) {
|
|
||||||
function _handleStream(stream, next, cb) {
|
|
||||||
stream.on("data", function(data) {
|
|
||||||
stream.pause();
|
|
||||||
cb(data, function() {
|
|
||||||
stream.resume();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on("error", next);
|
|
||||||
stream.on("end", next);
|
|
||||||
}
|
|
||||||
|
|
||||||
async.auto({
|
|
||||||
fromPadData : function(next) {
|
|
||||||
backend.getPadData(fromPadId, next);
|
|
||||||
},
|
|
||||||
toPadData : function(next) {
|
|
||||||
getPadData(toPadId, next);
|
|
||||||
},
|
|
||||||
padsExist : [ "fromPadData", "toPadData", function(r, next) {
|
|
||||||
if(!r.fromPadData)
|
|
||||||
return next(new Error("Pad "+fromPadId+" does not exist."));
|
|
||||||
if(!r.toPadData.writable)
|
|
||||||
return next(new Error("Destination pad is read-only."));
|
|
||||||
|
|
||||||
toPadId = r.toPadData.id;
|
|
||||||
|
|
||||||
next();
|
|
||||||
}],
|
|
||||||
copyMarkers : [ "padsExist", function(r, next) {
|
|
||||||
_handleStream(getPadMarkers(fromPadId, null), next, function(marker, cb) {
|
|
||||||
createMarker(toPadId, marker, cb);
|
|
||||||
});
|
|
||||||
}],
|
|
||||||
copyLines : [ "padsExist", function(r, next) {
|
|
||||||
_handleStream(getPadLines(fromPadId), next, function(line, cb) {
|
|
||||||
async.auto({
|
|
||||||
createLine : function(next) {
|
|
||||||
_createLine(toPadId, line, next);
|
|
||||||
},
|
|
||||||
getLinePoints : function(next) {
|
|
||||||
backend.getLinePoints(line.id, next);
|
|
||||||
},
|
|
||||||
setLinePoints : [ "createLine", "getLinePoints", function(r, next) {
|
|
||||||
_setLinePoints(toPadId, r.createLine.id, r.getLinePoints, next);
|
|
||||||
} ]
|
|
||||||
}, cb);
|
|
||||||
});
|
|
||||||
}],
|
|
||||||
copyViews : [ "padsExist", function(r, next) {
|
|
||||||
_handleStream(getViews(fromPadId), next, function(view, cb) {
|
|
||||||
createView(toPadId, view, function(err, newView) {
|
|
||||||
if(err)
|
|
||||||
return cb(err);
|
|
||||||
|
|
||||||
if(r.fromPadData.defaultView && r.fromPadData.defaultView.id == view.id && r.toPadData.defaultView == null)
|
|
||||||
updatePadData(toPadId, { defaultView: newView.id }, cb);
|
|
||||||
else
|
|
||||||
cb();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}]
|
|
||||||
}, callback);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
function _calculateRouting(line) {
|
|
||||||
if(line.mode == "track" && line.trackPoints && line.trackPoints.length >= 2) {
|
|
||||||
line.distance = utils.calculateDistance(line.trackPoints);
|
|
||||||
line.time = null;
|
|
||||||
|
|
||||||
routing._calculateZoomLevels(line.trackPoints);
|
|
||||||
|
|
||||||
for(var i=0; i<line.trackPoints.length; i++)
|
|
||||||
line.trackPoints[i].idx = i;
|
|
||||||
|
|
||||||
return Promise.resolve(line.trackPoints);
|
|
||||||
} else if(line.routePoints && line.routePoints.length >= 2 && line.mode && line.mode != "track") {
|
|
||||||
return routing.calculateRouting(line.routePoints, line.mode).then(function(routeData) {
|
|
||||||
line.distance = routeData.distance;
|
|
||||||
line.time = routeData.time;
|
|
||||||
for(var i=0; i<routeData.trackPoints.length; i++)
|
|
||||||
routeData.trackPoints[i].idx = i;
|
|
||||||
return routeData.trackPoints;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
line.distance = utils.calculateDistance(line.routePoints);
|
|
||||||
line.time = null;
|
|
||||||
|
|
||||||
var trackPoints = [ ];
|
|
||||||
for(var i=0; i<line.routePoints.length; i++) {
|
|
||||||
trackPoints.push(utils.extend({ }, line.routePoints[i], { zoom: 1, idx: i }));
|
|
||||||
}
|
|
||||||
return Promise.resolve(trackPoints);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getMarkerDataFunc(marker) {
|
|
||||||
return function(listener) {
|
|
||||||
if(!listener || !listener.bbox || !utils.isInBbox(marker, listener.bbox))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return marker;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getLinePoints(lineId, bboxWithZoom) {
|
|
||||||
return backend.getLinePointsByBbox(lineId, bboxWithZoom).then(function(data) {
|
|
||||||
// Get one more point outside of the bbox for each segment
|
|
||||||
var indexes = [ ];
|
|
||||||
for(var i=0; i<data.length; i++) {
|
|
||||||
if(i == 0 || data[i-1].idx != data[i].idx-1) // Beginning of segment
|
|
||||||
indexes.push(data[i].idx-1);
|
|
||||||
|
|
||||||
indexes.push(data[i].idx);
|
|
||||||
|
|
||||||
if(i == data.length-1 || data[i+1].idx != data[i].idx+1) // End of segment
|
|
||||||
indexes.push(data[i].idx+1);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(indexes.length == 0)
|
|
||||||
return [ ];
|
|
||||||
|
|
||||||
return backend.getLinePointsByIdx(lineId, indexes);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
connect : backend.connect,
|
|
||||||
getPadData : getPadData,
|
|
||||||
padIdExists : padIdExists,
|
|
||||||
createPad : createPad,
|
|
||||||
updatePadData : updatePadData,
|
|
||||||
getViews : getViews,
|
|
||||||
createView : createView,
|
|
||||||
updateView : updateView,
|
|
||||||
deleteView : deleteView,
|
|
||||||
getTypes : getTypes,
|
|
||||||
createType : createType,
|
|
||||||
updateType : updateType,
|
|
||||||
deleteType : deleteType,
|
|
||||||
getPadMarkers : getPadMarkers,
|
|
||||||
createMarker : createMarker,
|
|
||||||
updateMarker : updateMarker,
|
|
||||||
deleteMarker : deleteMarker,
|
|
||||||
getPadLines : getPadLines,
|
|
||||||
getPadLinesWithPoints : getPadLinesWithPoints,
|
|
||||||
getLineTemplate : getLineTemplate,
|
|
||||||
createLine : createLine,
|
|
||||||
updateLine : updateLine,
|
|
||||||
deleteLine : deleteLine,
|
|
||||||
getLinePoints : getLinePoints,
|
|
||||||
//copyPad : copyPad,
|
|
||||||
_defaultTypes : DEFAULT_TYPES
|
|
||||||
};
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
var util = require("util");
|
||||||
|
var events = require("events");
|
||||||
|
var Sequelize = require("sequelize");
|
||||||
|
|
||||||
|
var utils = require("../utils");
|
||||||
|
var config = require("../../config");
|
||||||
|
|
||||||
|
class Database extends events.EventEmitter {
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this._conn = new Sequelize(config.db.database, config.db.user, config.db.password, {
|
||||||
|
dialect: config.db.type,
|
||||||
|
host: config.db.host,
|
||||||
|
port: config.db.port,
|
||||||
|
define: {
|
||||||
|
timestamps: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for(let func of this._init)
|
||||||
|
func.call(this);
|
||||||
|
|
||||||
|
for(let func of this._afterInit)
|
||||||
|
func.call(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(force) {
|
||||||
|
return this._conn.authenticate().then(() => {
|
||||||
|
return this._conn.sync({ force: !!force });
|
||||||
|
}).then(() => {
|
||||||
|
this._runMigrations()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Database.prototype._init = [ ];
|
||||||
|
Database.prototype._afterInit = [ ];
|
||||||
|
|
||||||
|
require("./migrations")(Database);
|
||||||
|
require("./helpers")(Database);
|
||||||
|
|
||||||
|
require("./pad")(Database);
|
||||||
|
require("./marker")(Database);
|
||||||
|
require("./line")(Database);
|
||||||
|
require("./view")(Database);
|
||||||
|
require("./type")(Database);
|
||||||
|
|
||||||
|
module.exports = Database;
|
|
@ -0,0 +1,271 @@
|
||||||
|
var Sequelize = require("sequelize");
|
||||||
|
var stream = require("stream");
|
||||||
|
|
||||||
|
var utils = require("../utils");
|
||||||
|
|
||||||
|
module.exports = function(Database) {
|
||||||
|
utils.extend(Database.prototype, {
|
||||||
|
_TYPES: {
|
||||||
|
get lat() {
|
||||||
|
return {
|
||||||
|
type: Sequelize.FLOAT(9, 6),
|
||||||
|
allowNull: false,
|
||||||
|
validate: {
|
||||||
|
min: -90,
|
||||||
|
max: 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
get lon() {
|
||||||
|
return {
|
||||||
|
type: Sequelize.FLOAT(9, 6),
|
||||||
|
allowNull: false,
|
||||||
|
validate: {
|
||||||
|
min: -180,
|
||||||
|
max: 180
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
validateColour: { is: /^[a-fA-F0-9]{3}([a-fA-F0-9]{3})?$/ },
|
||||||
|
|
||||||
|
dataDefinition: {
|
||||||
|
"name" : { type: Sequelize.TEXT, allowNull: false },
|
||||||
|
"value" : { type: Sequelize.TEXT, allowNull: false }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_updateObjectStyles(objectStream, isLine) {
|
||||||
|
var t = this;
|
||||||
|
|
||||||
|
if(!(objectStream instanceof stream.Readable))
|
||||||
|
objectStream = new utils.ArrayStream([ objectStream ]);
|
||||||
|
|
||||||
|
var types = { };
|
||||||
|
return utils.streamEachPromise(objectStream, (object) => {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!types[object.typeId]) {
|
||||||
|
return t.getType(object.padId, object.typeId).then((type) => {
|
||||||
|
if(type == null)
|
||||||
|
throw "Type "+object.typeId+" does not exist.";
|
||||||
|
|
||||||
|
return types[object.typeId] = type;
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
return types[object.typeId];
|
||||||
|
}).then((type) => {
|
||||||
|
var update = { };
|
||||||
|
|
||||||
|
if(type.colourFixed && object.colour != type.defaultColour)
|
||||||
|
update.colour = type.defaultColour;
|
||||||
|
if(!isLine && type.sizeFixed && object.size != type.defaultSize)
|
||||||
|
update.size = type.defaultSize;
|
||||||
|
if(!isLine && type.symbolFixed && object.symbol != type.defaultSymbol)
|
||||||
|
update.symbol = type.defaultSymbol;
|
||||||
|
if(isLine && type.widthFixed && object.width != type.defaultWidth)
|
||||||
|
update.width = type.defaultWidth;
|
||||||
|
if(isLine && type.modeFixed && object.mode != "track" && object.mode != type.defaultMode)
|
||||||
|
update.mode = type.defaultMode;
|
||||||
|
|
||||||
|
types[object.typeId].fields.forEach((field) => {
|
||||||
|
if(field.type == "dropdown" && (field.controlColour || (!isLine && field.controlSize) || (!isLine && field.controlSymbol) || (isLine && field.controlWidth))) {
|
||||||
|
var _find = (value) => {
|
||||||
|
for(var j=0; j<(field.options || []).length; j++) {
|
||||||
|
if(field.options[j].key == value)
|
||||||
|
return field.options[j];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
var option = _find(object.data[field.name]) || _find(field.default) || field.options[0];
|
||||||
|
|
||||||
|
if(option != null) {
|
||||||
|
if(field.controlColour && object.colour != option.colour)
|
||||||
|
update.colour = option.colour;
|
||||||
|
if(!isLine && field.controlSize && object.size != option.size)
|
||||||
|
update.size = option.size;
|
||||||
|
if(!isLine && field.controlSymbol && object.symbol != option.symbol)
|
||||||
|
update.symbol = option.symbol;
|
||||||
|
if(isLine && field.controlWidth && object.width != option.width)
|
||||||
|
update.width = option.width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var ret = [ ];
|
||||||
|
|
||||||
|
if(Object.keys(update).length > 0) {
|
||||||
|
utils.extend(object, update);
|
||||||
|
|
||||||
|
if(object.id) // Objects from getLineTemplate() do not have an ID
|
||||||
|
ret.push((isLine ? t.updateLine : t.updateMarker).call(t, object.padId, object.id, update, true));
|
||||||
|
|
||||||
|
if(object.id && isLine && "mode" in update) {
|
||||||
|
ret.push(t._calculateRouting(object).then(function(trackPoints) {
|
||||||
|
return t._setLinePoints(object.padId, object.id, trackPoints);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(ret);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_makeNotNullForeignKey(type, field, error) {
|
||||||
|
return {
|
||||||
|
as: type,
|
||||||
|
onDelete: error ? "RESTRICT" : "CASCADE",
|
||||||
|
foreignKey: { name: field, allowNull: false }
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_getPadObjects(type, padId, condition) {
|
||||||
|
var ret = new utils.ArrayStream();
|
||||||
|
|
||||||
|
var o = this._conn.model("Pad").build({ id: padId });
|
||||||
|
this._conn.model("Pad").build({ id: padId })["get"+type+"s"](condition).then((objs) => {
|
||||||
|
objs.forEach((it) => {
|
||||||
|
if(it[type+"Data"] != null) {
|
||||||
|
it.data = this._dataFromArr(it[type+"Data"]);
|
||||||
|
it.setDataValue("data", it.data); // For JSON.stringify()
|
||||||
|
it.setDataValue(type+"Data", undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ret.receiveArray(null, objs);
|
||||||
|
}, (err) => {
|
||||||
|
ret.receiveArray(err);
|
||||||
|
});
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
|
||||||
|
_createPadObject(type, padId, data) {
|
||||||
|
var obj = this._conn.model(type).build(data);
|
||||||
|
obj.padId = padId;
|
||||||
|
return obj.save();
|
||||||
|
},
|
||||||
|
|
||||||
|
_createPadObjectWithData(type, padId, data) {
|
||||||
|
return this._createPadObject(type, padId, data).then((obj) => {
|
||||||
|
if(data.data != null) {
|
||||||
|
obj.data = data.data;
|
||||||
|
obj.setDataValue("data", obj.data); // For JSON.stringify()
|
||||||
|
return this._setObjectData(type, obj.id, data.data).then(() => {
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
obj.data = { };
|
||||||
|
obj.setDataValue("data", obj.data); // For JSON.stringify()
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_updatePadObject(type, padId, objId, data) {
|
||||||
|
return this._conn.model(type).update(data, { where: { id: objId, padId: padId } }).then((res) => {
|
||||||
|
if(res[0] == 0)
|
||||||
|
throw new Error(type + " " + objId + " of pad " + padId + "could not be found.");
|
||||||
|
|
||||||
|
return this._conn.model(type).findById(objId);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_updatePadObjectWithData(type, padId, objId, data) {
|
||||||
|
return Promise.all([
|
||||||
|
this._updatePadObject(type, padId, objId, data),
|
||||||
|
data.data != null ? this._setObjectData(type, objId, data.data) : this._getObjectData(type, objId)
|
||||||
|
]).then((results) => {
|
||||||
|
var obj = results[0];
|
||||||
|
obj.data = (data.data != null ? data.data : results[1]);
|
||||||
|
obj.setDataValue("data", obj.data); // For JSON.stringify()
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_deletePadObject(type, padId, objId) {
|
||||||
|
return this._conn.model(type).findOne({ where: { id: objId, padId: padId }}).then((obj) => {
|
||||||
|
if(obj == null)
|
||||||
|
throw new Error(type + " " + objId + " of pad " + padId + " could not be found.");
|
||||||
|
|
||||||
|
return obj.destroy().then(() => {
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_deletePadObjectWithData(type, padId, objId) {
|
||||||
|
return this._setObjectData(type, objId, { }).then(() => {
|
||||||
|
return this._deletePadObject(type, padId, objId); // Return the object
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_dataToArr(data, extend) {
|
||||||
|
var dataArr = [ ];
|
||||||
|
for(var i in data)
|
||||||
|
dataArr.push(utils.extend({ name: i, value: data[i] }, extend));
|
||||||
|
return dataArr;
|
||||||
|
},
|
||||||
|
|
||||||
|
_dataFromArr(dataArr) {
|
||||||
|
var data = { };
|
||||||
|
for(var i=0; i<dataArr.length; i++)
|
||||||
|
data[dataArr[i].name] = dataArr[i].value;
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
_getObjectData(type, objId) {
|
||||||
|
var filter = { };
|
||||||
|
filter[type.toLowerCase()+"Id"] = objId;
|
||||||
|
|
||||||
|
return this._conn.model(type+"Data").findAll({ where: filter}).then((dataArr) => {
|
||||||
|
return this._dataFromArr(dataArr);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_setObjectData(type, objId, data) {
|
||||||
|
var model = this._conn.model(type+"Data");
|
||||||
|
var idObj = { };
|
||||||
|
idObj[type.toLowerCase()+"Id"] = objId;
|
||||||
|
|
||||||
|
return model.destroy({ where: idObj}).then(() => {
|
||||||
|
return model.bulkCreate(this._dataToArr(data, idObj));
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_makeBboxCondition(bbox, prefix) {
|
||||||
|
if(!bbox)
|
||||||
|
return { };
|
||||||
|
|
||||||
|
prefix = prefix || "";
|
||||||
|
|
||||||
|
var cond = (key, value) => {
|
||||||
|
var ret = { };
|
||||||
|
ret[prefix+key] = value;
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
var conditions = [ ];
|
||||||
|
conditions.push(cond("lat", { lte: bbox.top, gte: bbox.bottom }));
|
||||||
|
|
||||||
|
if(bbox.right < bbox.left) // Bbox spans over lon=180
|
||||||
|
conditions.push(Sequelize.or(cond("lon", { gte: bbox.left }), cond("lon", { lte: bbox.right })));
|
||||||
|
else
|
||||||
|
conditions.push(cond("lon", { gte: bbox.left, lte: bbox.right }));
|
||||||
|
|
||||||
|
if(bbox.except) {
|
||||||
|
var exceptConditions = [ ];
|
||||||
|
exceptConditions.push(Sequelize.or(cond("lat", { gt: bbox.except.top }), cond("lat", { lt: bbox.except.bottom })));
|
||||||
|
|
||||||
|
if(bbox.except.right < bbox.except.left)
|
||||||
|
exceptConditions.push(cond("lon", { lt: bbox.except.left, gt: bbox.except.right }));
|
||||||
|
else
|
||||||
|
exceptConditions.push(Sequelize.or(cond("lon", { lt: bbox.except.left }), cond("lon", { gt: bbox.except.right })));
|
||||||
|
conditions.push(Sequelize.or.apply(Sequelize, exceptConditions));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Sequelize.and.apply(Sequelize, conditions);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,44 @@
|
||||||
|
var Sequelize = require("sequelize");
|
||||||
|
|
||||||
|
var utils = require("../utils");
|
||||||
|
|
||||||
|
module.exports = function(Database) {
|
||||||
|
Database.prototype._init.push(function() {
|
||||||
|
this._conn.define("History", {
|
||||||
|
time: { type: Sequelize.DATE, allowNull: false, defaultValue: Sequelize.NOW },
|
||||||
|
type: { type: Sequelize.ENUM("marker", "line", "view", "type", "pad"), allowNull: false },
|
||||||
|
action: { type: Sequelize.ENUM("create", "update", "delete"), allowNull: false },
|
||||||
|
objectBefore: {
|
||||||
|
type: Sequelize.TEXT,
|
||||||
|
allowNull: true,
|
||||||
|
get: function() {
|
||||||
|
return JSON.parse(this.getDataValue("objectBefore"));
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
|
this.setDataValue("objectBefore", JSON.stringify(v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Database.prototype._afterInit.push(function() {
|
||||||
|
this._conn.model("Pad").hasMany(this._conn.model("History"), this._makeNotNullForeignKey("History", "padId"));
|
||||||
|
this._conn.model("History").belongsTo(this._conn.model("Pad"), this._makeNotNullForeignKey("pad", "padId"));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// =====================================================================================================================
|
||||||
|
|
||||||
|
|
||||||
|
utils.extend(Database.prototype, {
|
||||||
|
addHistoryEntry(padId, data) {
|
||||||
|
return this._createPadObject("History", padId, data).then((historyEntry) => {
|
||||||
|
this.emit("addHistoryEntry", historyEntry);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getHistory(padId) {
|
||||||
|
return this._getPadObjects("History", padId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,301 @@
|
||||||
|
var Sequelize = require("sequelize");
|
||||||
|
var underscore = require("underscore");
|
||||||
|
|
||||||
|
var utils = require("../utils");
|
||||||
|
var routing = require("../routing");
|
||||||
|
|
||||||
|
module.exports = function(Database) {
|
||||||
|
Database.prototype._init.push(function() {
|
||||||
|
this._conn.define("Line", {
|
||||||
|
routePoints : {
|
||||||
|
type: Sequelize.TEXT,
|
||||||
|
allowNull: false,
|
||||||
|
get: function() {
|
||||||
|
var routePoints = this.getDataValue("routePoints");
|
||||||
|
return routePoints != null ? JSON.parse(routePoints) : routePoints;
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
|
for(var i=0; i<v.length; i++) {
|
||||||
|
v[i].lat = 1*v[i].lat.toFixed(6);
|
||||||
|
v[i].lon = 1*v[i].lon.toFixed(6);
|
||||||
|
}
|
||||||
|
this.setDataValue("routePoints", JSON.stringify(v));
|
||||||
|
},
|
||||||
|
validate: {
|
||||||
|
minTwo: function(val) {
|
||||||
|
var routePoints = JSON.parse(val);
|
||||||
|
if(!Array.isArray(routePoints))
|
||||||
|
throw new Error("routePoints is not an array");
|
||||||
|
if(routePoints.length < 2)
|
||||||
|
throw new Error("A line cannot have less than two route points.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mode : { type: Sequelize.ENUM("", "car", "bicycle", "pedestrian", "track"), allowNull: false, defaultValue: "" },
|
||||||
|
colour : { type: Sequelize.STRING(6), allowNull: false, defaultValue: "0000ff", validate: this._TYPES.validateColour },
|
||||||
|
width : { type: Sequelize.INTEGER.UNSIGNED, allowNull: false, defaultValue: 4, validate: { min: 1 } },
|
||||||
|
name : { type: Sequelize.TEXT, allowNull: true, get: function() { return this.getDataValue("name") || "Untitled line"; } },
|
||||||
|
distance : { type: Sequelize.FLOAT(24, 2).UNSIGNED, allowNull: true },
|
||||||
|
time : { type: Sequelize.INTEGER.UNSIGNED, allowNull: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
this._conn.define("LinePoint", {
|
||||||
|
lat: this._TYPES.lat,
|
||||||
|
lon: this._TYPES.lon,
|
||||||
|
zoom: { type: Sequelize.INTEGER.UNSIGNED, allowNull: false, validate: { min: 1, max: 20 } },
|
||||||
|
idx: { type: Sequelize.INTEGER.UNSIGNED, allowNull: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
this._conn.define("LineData", this._TYPES.dataDefinition);
|
||||||
|
});
|
||||||
|
|
||||||
|
Database.prototype._afterInit.push(function() {
|
||||||
|
var Line = this._conn.model("Line");
|
||||||
|
var LinePoint = this._conn.model("LinePoint");
|
||||||
|
var LineData = this._conn.model("LineData");
|
||||||
|
|
||||||
|
Line.belongsTo(this._conn.model("Pad"), this._makeNotNullForeignKey("pad", "padId"));
|
||||||
|
this._conn.model("Pad").hasMany(Line, { foreignKey: "padId" });
|
||||||
|
|
||||||
|
// TODO: Cascade
|
||||||
|
Line.belongsTo(this._conn.model("Type"), this._makeNotNullForeignKey("type", "typeId", true));
|
||||||
|
|
||||||
|
LinePoint.belongsTo(Line, this._makeNotNullForeignKey("line", "lineId"));
|
||||||
|
Line.hasMany(LinePoint, { foreignKey: "lineId" });
|
||||||
|
|
||||||
|
LineData.belongsTo(Line, this._makeNotNullForeignKey("line", "lineId"));
|
||||||
|
Line.hasMany(LineData, { foreignKey: "lineId" });
|
||||||
|
});
|
||||||
|
|
||||||
|
// =====================================================================================================================
|
||||||
|
|
||||||
|
utils.extend(Database.prototype, {
|
||||||
|
getPadLines(padId, fields) {
|
||||||
|
var cond = { include: [ this._conn.model("LineData") ] };
|
||||||
|
if(fields)
|
||||||
|
cond.attributes = (typeof fields == "string" ? fields.split(/\s+/) : fields);
|
||||||
|
|
||||||
|
return this._getPadObjects("Line", padId, cond);
|
||||||
|
},
|
||||||
|
|
||||||
|
getPadLinesByType(padId, typeId) {
|
||||||
|
return this._getPadObjects("Line", padId, { where: { typeId: typeId }, include: [ this._conn.model("LineData") ] });
|
||||||
|
},
|
||||||
|
|
||||||
|
getPadLinesWithPoints(padId, bboxWithZoom) {
|
||||||
|
return utils.filterStreamPromise(this.getPadLines(padId), (data) => {
|
||||||
|
return this.getLinePoints(data.id, bboxWithZoom).then((trackPoints) => {
|
||||||
|
data.trackPoints = trackPoints;
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getLineTemplate(padId, data) {
|
||||||
|
return utils.promiseAuto({
|
||||||
|
lineTemplate: () => {
|
||||||
|
return JSON.parse(JSON.stringify(this._conn.model("Line").build(utils.extend({ }, data, { padId: padId, data: data.data || { } }))));
|
||||||
|
},
|
||||||
|
|
||||||
|
type: this.getType(padId, data.typeId),
|
||||||
|
|
||||||
|
styles: (lineTemplate, type) => {
|
||||||
|
if(type.defaultColour)
|
||||||
|
lineTemplate.colour = type.defaultColour;
|
||||||
|
if(type.defaultWidth)
|
||||||
|
lineTemplate.width = type.defaultWidth;
|
||||||
|
if(type.defaultMode)
|
||||||
|
lineTemplate.mode = type.defaultMode;
|
||||||
|
|
||||||
|
return this._updateObjectStyles(lineTemplate, true);
|
||||||
|
}
|
||||||
|
}).then((res) => {
|
||||||
|
return res.lineTemplate;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getLine(padId, lineId) {
|
||||||
|
return this._conn.model("Line").findOne({ where: { id: lineId, padId: padId }, include: [ this._conn.model("LineData") ] }).then(line => {
|
||||||
|
if(line == null)
|
||||||
|
throw new Error("Line " + lineId + " of pad " + padId + " could not be found.");
|
||||||
|
|
||||||
|
return line;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
createLine(padId, data) {
|
||||||
|
return utils.promiseAuto({
|
||||||
|
defaultVals: this.getType(padId, data.typeId).then((type) => {
|
||||||
|
if(type.defaultColour && !("colour" in data))
|
||||||
|
data.colour = type.defaultColour;
|
||||||
|
if(type.defaultWidth && !("width" in data))
|
||||||
|
data.width = type.defaultWidth;
|
||||||
|
if(type.defaultMode && !("mode" in data))
|
||||||
|
data.mode = type.defaultMode;
|
||||||
|
}),
|
||||||
|
|
||||||
|
routing: (defaultVals) => {
|
||||||
|
return this._calculateRouting(data);
|
||||||
|
},
|
||||||
|
|
||||||
|
createLine: (routing, defaultVals) => {
|
||||||
|
var dataCopy = utils.extend({ }, data);
|
||||||
|
delete dataCopy.trackPoints; // They came if mode is track
|
||||||
|
|
||||||
|
return this._createPadObjectWithData("Line", padId, dataCopy);
|
||||||
|
},
|
||||||
|
|
||||||
|
lineEvent: (createLine) => {
|
||||||
|
// We have to emit this before calling _setLinePoints so that this event is sent to the client first
|
||||||
|
this.emit("line", padId, createLine);
|
||||||
|
},
|
||||||
|
|
||||||
|
setLinePoints: (routing, createLine, lineEvent) => {
|
||||||
|
return this._setLinePoints(padId, createLine.id, routing);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateStyle: (createLine) => {
|
||||||
|
return this._updateObjectStyles(createLine, true);
|
||||||
|
}
|
||||||
|
}).then(res => res.createLine);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateLine(padId, lineId, data, doNotUpdateStyles) {
|
||||||
|
return utils.promiseAuto({
|
||||||
|
originalLine: this.getLine(padId, lineId),
|
||||||
|
|
||||||
|
routing: (originalLine) => {
|
||||||
|
if(data.routePoints == null)
|
||||||
|
data.routePoints = originalLine.routePoints;
|
||||||
|
|
||||||
|
if(data.mode == null)
|
||||||
|
data.mode = originalLine.mode || "";
|
||||||
|
|
||||||
|
if((data.mode == "track" && data.trackPoints) || !underscore.isEqual(data.routePoints, originalLine.routePoints) || data.mode != originalLine.mode)
|
||||||
|
return this._calculateRouting(data); // Also sets data.distance and data.time
|
||||||
|
},
|
||||||
|
|
||||||
|
newLine: (routing) => {
|
||||||
|
var dataCopy = utils.extend({ }, data);
|
||||||
|
delete dataCopy.trackPoints; // They came if mode is track
|
||||||
|
|
||||||
|
return this._updatePadObjectWithData("Line", padId, lineId, dataCopy);
|
||||||
|
},
|
||||||
|
|
||||||
|
updateStyle: (newLine) => {
|
||||||
|
if(!doNotUpdateStyles)
|
||||||
|
return this._updateObjectStyles(newLine, true); // Modifies newLine
|
||||||
|
},
|
||||||
|
|
||||||
|
linePoints: (newLine, routing) => {
|
||||||
|
if(routing)
|
||||||
|
return this._setLinePoints(newLine.padId, lineId, routing);
|
||||||
|
}
|
||||||
|
}).then((res) => {
|
||||||
|
this.emit("line", padId, res.newLine);
|
||||||
|
|
||||||
|
return res.newLine;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_setLinePoints(padId, lineId, trackPoints, _noEvent) {
|
||||||
|
return this._conn.model("LinePoint").destroy({ where: { lineId: lineId } }).then(() => {
|
||||||
|
var create = [ ];
|
||||||
|
for(var i=0; i<trackPoints.length; i++) {
|
||||||
|
create.push(utils.extend({ }, trackPoints[i], { lineId: lineId }));
|
||||||
|
}
|
||||||
|
|
||||||
|
return this._conn.model("LinePoint").bulkCreate(create);
|
||||||
|
}).then((points) => {
|
||||||
|
if(!_noEvent)
|
||||||
|
this.emit("linePoints", padId, lineId, points);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteLine(padId, lineId) {
|
||||||
|
return utils.promiseAuto({
|
||||||
|
line: this._deletePadObjectWithData("Line", padId, lineId),
|
||||||
|
points: this._setLinePoints(padId, lineId, [ ], true)
|
||||||
|
}).then((res) => {
|
||||||
|
this.emit("deleteLine", padId, { id: lineId });
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getLinePointsForPad(padId, bboxWithZoom) {
|
||||||
|
return utils.filterStreamPromise(this.getPadLines(padId, "id"), (line) => {
|
||||||
|
return this.getLinePoints(line.id, bboxWithZoom).then((trackPoints) => {
|
||||||
|
if(trackPoints.length >= 2)
|
||||||
|
return { id: line.id, trackPoints: trackPoints };
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getLinePoints(lineId, bboxWithZoom) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
return this._conn.model("Line").build({ id: lineId }).getLinePoints({
|
||||||
|
where: Sequelize.and(this._makeBboxCondition(bboxWithZoom), bboxWithZoom ? { zoom: { lte: bboxWithZoom.zoom } } : null),
|
||||||
|
attributes: [ "idx" ],
|
||||||
|
order: "idx"
|
||||||
|
});
|
||||||
|
}).then((data) => {
|
||||||
|
// Get one more point outside of the bbox for each segment
|
||||||
|
var indexes = [ ];
|
||||||
|
for(var i=0; i<data.length; i++) {
|
||||||
|
if(i == 0 || data[i-1].idx != data[i].idx-1) // Beginning of segment
|
||||||
|
indexes.push(data[i].idx-1);
|
||||||
|
|
||||||
|
indexes.push(data[i].idx);
|
||||||
|
|
||||||
|
if(i == data.length-1 || data[i+1].idx != data[i].idx+1) // End of segment
|
||||||
|
indexes.push(data[i].idx+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(indexes.length == 0)
|
||||||
|
return [ ];
|
||||||
|
|
||||||
|
return this.getLinePointsByIdx(lineId, indexes);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getLinePointsByIdx(lineId, indexes) {
|
||||||
|
return this._conn.model("Line").build({ id: lineId }).getLinePoints({
|
||||||
|
where: { idx: indexes },
|
||||||
|
attributes: [ "lon", "lat", "idx" ],
|
||||||
|
order: "idx"
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_calculateRouting(line) {
|
||||||
|
if(line.mode == "track" && line.trackPoints && line.trackPoints.length >= 2) {
|
||||||
|
line.distance = utils.calculateDistance(line.trackPoints);
|
||||||
|
line.time = null;
|
||||||
|
|
||||||
|
routing._calculateZoomLevels(line.trackPoints);
|
||||||
|
|
||||||
|
for(var i=0; i<line.trackPoints.length; i++)
|
||||||
|
line.trackPoints[i].idx = i;
|
||||||
|
|
||||||
|
return Promise.resolve(line.trackPoints);
|
||||||
|
} else if(line.routePoints && line.routePoints.length >= 2 && line.mode && line.mode != "track") {
|
||||||
|
return routing.calculateRouting(line.routePoints, line.mode).then((routeData) => {
|
||||||
|
line.distance = routeData.distance;
|
||||||
|
line.time = routeData.time;
|
||||||
|
for(var i=0; i<routeData.trackPoints.length; i++)
|
||||||
|
routeData.trackPoints[i].idx = i;
|
||||||
|
return routeData.trackPoints;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
line.distance = utils.calculateDistance(line.routePoints);
|
||||||
|
line.time = null;
|
||||||
|
|
||||||
|
var trackPoints = [ ];
|
||||||
|
for(var i=0; i<line.routePoints.length; i++) {
|
||||||
|
trackPoints.push(utils.extend({ }, line.routePoints[i], { zoom: 1, idx: i }));
|
||||||
|
}
|
||||||
|
return Promise.resolve(trackPoints);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,85 @@
|
||||||
|
var Sequelize = require("sequelize");
|
||||||
|
|
||||||
|
var utils = require("../utils");
|
||||||
|
|
||||||
|
module.exports = function(Database) {
|
||||||
|
Database.prototype._init.push(function() {
|
||||||
|
this._conn.define("Marker", {
|
||||||
|
"lat" : this._TYPES.lat,
|
||||||
|
"lon" : this._TYPES.lon,
|
||||||
|
name : { type: Sequelize.TEXT, allowNull: true, get: function() { return this.getDataValue("name") || "Untitled marker"; } },
|
||||||
|
colour : { type: Sequelize.STRING(6), allowNull: false, defaultValue: "ff0000", validate: this._TYPES.validateColour },
|
||||||
|
size : { type: Sequelize.INTEGER.UNSIGNED, allowNull: false, defaultValue: 25, validate: { min: 15 } },
|
||||||
|
symbol : { type: Sequelize.TEXT, allowNull: true }
|
||||||
|
});
|
||||||
|
|
||||||
|
this._conn.define("MarkerData", this._TYPES.dataDefinition);
|
||||||
|
});
|
||||||
|
|
||||||
|
Database.prototype._afterInit.push(function() {
|
||||||
|
var Marker = this._conn.model("Marker");
|
||||||
|
this._conn.model("Pad").hasMany(Marker, this._makeNotNullForeignKey("Markers", "padId"));
|
||||||
|
Marker.belongsTo(this._conn.model("Pad"), this._makeNotNullForeignKey("pad", "padId"));
|
||||||
|
Marker.belongsTo(this._conn.model("Type"), this._makeNotNullForeignKey("type", "typeId", true));
|
||||||
|
|
||||||
|
this._conn.model("MarkerData").belongsTo(Marker, this._makeNotNullForeignKey("marker", "markerId"));
|
||||||
|
Marker.hasMany(this._conn.model("MarkerData"), { foreignKey: "markerId" });
|
||||||
|
});
|
||||||
|
|
||||||
|
// =====================================================================================================================
|
||||||
|
|
||||||
|
utils.extend(Database.prototype, {
|
||||||
|
getPadMarkers(padId, bbox) {
|
||||||
|
return this._getPadObjects("Marker", padId, { where: this._makeBboxCondition(bbox), include: [ this._conn.model("MarkerData") ] });
|
||||||
|
},
|
||||||
|
|
||||||
|
getPadMarkersByType(padId, typeId) {
|
||||||
|
return this._getPadObjects("Marker", padId, { where: { padId: padId, typeId: typeId }, include: [ this._conn.model("MarkerData") ] });
|
||||||
|
},
|
||||||
|
|
||||||
|
createMarker(padId, data) {
|
||||||
|
return utils.promiseAuto({
|
||||||
|
type: this.getType(padId, data.typeId),
|
||||||
|
create: (type) => {
|
||||||
|
if(type.defaultColour)
|
||||||
|
data.colour = type.defaultColour;
|
||||||
|
if(type.defaultSize)
|
||||||
|
data.size = type.defaultSize;
|
||||||
|
if(type.defaultSymbol)
|
||||||
|
data.symbol = type.defaultSymbol;
|
||||||
|
|
||||||
|
return this._createPadObjectWithData("Marker", padId, data);
|
||||||
|
},
|
||||||
|
styles: (create) => {
|
||||||
|
return this._updateObjectStyles(create, false)
|
||||||
|
}
|
||||||
|
}).then((res) => {
|
||||||
|
this.emit("marker", padId, res.create);
|
||||||
|
|
||||||
|
return res.create;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateMarker(padId, markerId, data, doNotUpdateStyles) {
|
||||||
|
return utils.promiseAuto({
|
||||||
|
update: this._updatePadObjectWithData("Marker", padId, markerId, data),
|
||||||
|
updateStyles: (update) => {
|
||||||
|
if(!doNotUpdateStyles)
|
||||||
|
return this._updateObjectStyles(update, false);
|
||||||
|
}
|
||||||
|
}).then((res) => {
|
||||||
|
this.emit("marker", padId, res.update);
|
||||||
|
|
||||||
|
return res.update;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteMarker(padId, markerId) {
|
||||||
|
this._deletePadObjectWithData("Marker", padId, markerId).then(del => {
|
||||||
|
this.emit("deleteMarker", padId, { id: del.id });
|
||||||
|
|
||||||
|
return del;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,89 @@
|
||||||
|
var Sequelize = require("sequelize");
|
||||||
|
|
||||||
|
var utils = require("../utils");
|
||||||
|
|
||||||
|
module.exports = function(Database) {
|
||||||
|
utils.extend(Database.prototype, {
|
||||||
|
_runMigrations() {
|
||||||
|
var queryInterface = this._conn.getQueryInterface();
|
||||||
|
|
||||||
|
var renameColMigrations = Promise.all([
|
||||||
|
queryInterface.describeTable('Lines').then((attributes) => {
|
||||||
|
var promises = [ ];
|
||||||
|
|
||||||
|
// Rename Line.points to Line.routePoints
|
||||||
|
if(attributes.points) {
|
||||||
|
promises.push(queryInterface.renameColumn('Lines', 'points', 'routePoints'));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Change routing type "shortest" / "fastest" to "car", add type "track"
|
||||||
|
if(attributes.mode.type.indexOf("shortest") != -1) {
|
||||||
|
promises.push(
|
||||||
|
Promise.resolve().then(() => {
|
||||||
|
return queryInterface.changeColumn('Lines', 'mode', {
|
||||||
|
type: Sequelize.ENUM("", "shortest", "fastest", "car", "bicycle", "pedestrian"), allowNull: false, defaultValue: ""
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
return this._conn.model("Line").update({ mode: "car" }, { where: { mode: { $in: [ "fastest", "shortest" ] } } });
|
||||||
|
}).then(() => {
|
||||||
|
return queryInterface.changeColumn('Lines', 'mode', {
|
||||||
|
type: Sequelize.ENUM("", "car", "bicycle", "pedestrian", "track"), allowNull: false, defaultValue: ""
|
||||||
|
});
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
|
||||||
|
var changeColMigrations = Promise.all([ 'Pads', 'Markers', 'Lines' ].map((table) => {
|
||||||
|
// allow null on Pad.name, Marker.name, Line.name
|
||||||
|
return queryInterface.describeTable(table).then((attributes) => {
|
||||||
|
if(!attributes.name.allowNull)
|
||||||
|
return queryInterface.changeColumn(table, 'name', { type: Sequelize.TEXT, allowNull: true });
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
var addColMigrations = renameColMigrations.then(() => {
|
||||||
|
return Promise.all([ 'Marker', 'Type' ].map((table) => {
|
||||||
|
queryInterface.describeTable(table+"s").then((attributes) => {
|
||||||
|
var promises = [ ];
|
||||||
|
var model = this._conn.model(table);
|
||||||
|
for(var attribute in model.attributes) {
|
||||||
|
if(!attributes[attribute])
|
||||||
|
promises.push(queryInterface.addColumn(table+"s", attribute, model.attributes[attribute]));
|
||||||
|
}
|
||||||
|
return Promise.all(promises);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
/*queryInterface.describeTable('Markers').then(function(attributes) {
|
||||||
|
var promises = [ ];
|
||||||
|
|
||||||
|
// Add size and symbol columns
|
||||||
|
if(!attributes.size)
|
||||||
|
promises.push(queryInterface.addColumn('Markers', 'size', Marker.attributes.size));
|
||||||
|
if(!attributes.symbol)
|
||||||
|
promises.push(queryInterface.addColumn('Markers', 'symbol', Marker.attributes.symbol));
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}),
|
||||||
|
|
||||||
|
queryInterface.describeTable('Types').then(function(attributes) {
|
||||||
|
return Promise.all([ 'defaultColour', 'colourFixed', 'defaultSize', 'sizeFixed', 'defaultSymbol', 'symbolFixed', 'defaultWidth', 'widthFixed', 'defaultMode', 'modeFixed' ].map(function(col) {
|
||||||
|
if(!attributes[col])
|
||||||
|
return queryInterface.addColumn('Types', col, Type.attributes[col]);
|
||||||
|
}));
|
||||||
|
}),
|
||||||
|
|
||||||
|
queryInterface.describeTable('Views').then(function(attributes) {
|
||||||
|
if(!attributes.filter)
|
||||||
|
return queryInterface.addColumn('Views', 'filter', View.attributes.filter);
|
||||||
|
})*/
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all([ renameColMigrations, changeColMigrations, addColMigrations ]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,193 @@
|
||||||
|
var Sequelize = require("sequelize");
|
||||||
|
|
||||||
|
var utils = require("../utils");
|
||||||
|
|
||||||
|
module.exports = function(Database) {
|
||||||
|
Database.prototype._init.push(function() {
|
||||||
|
this._conn.define("Pad", {
|
||||||
|
id : { type: Sequelize.STRING, allowNull: false, primaryKey: true, validate: { is: /^.+$/ } },
|
||||||
|
name: { type: Sequelize.TEXT, allowNull: true, get: function() { return this.getDataValue("name") || "New FacilMap"; } },
|
||||||
|
writeId: { type: Sequelize.STRING, allowNull: false, validate: { is: /^.+$/ } }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Database.prototype._afterInit.push(function() {
|
||||||
|
this._conn.model("Pad").belongsTo(this._conn.model("View"), { as: "defaultView", foreignKey: "defaultViewId", constraints: false });
|
||||||
|
});
|
||||||
|
|
||||||
|
// =====================================================================================================================
|
||||||
|
|
||||||
|
utils.extend(Database.prototype, {
|
||||||
|
padIdExists(padId) {
|
||||||
|
return this._conn.model("Pad").count({ where: { $or: [ { id: padId }, { writeId: padId } ] } }).then(function(num) {
|
||||||
|
return num > 0;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getPadData(padId) {
|
||||||
|
return this._conn.model("Pad").findOne({ where: { id: padId }, include: [ { model: this._conn.model("View"), as: "defaultView" } ]});
|
||||||
|
},
|
||||||
|
|
||||||
|
getPadDataByWriteId(writeId) {
|
||||||
|
return this._conn.model("Pad").findOne({ where: { writeId: writeId }, include: [ { model: this._conn.model("View"), as: "defaultView" } ] });
|
||||||
|
},
|
||||||
|
|
||||||
|
createPad(data) {
|
||||||
|
return utils.promiseAuto({
|
||||||
|
validate: () => {
|
||||||
|
if(!data.id || data.id.length == 0)
|
||||||
|
throw "Invalid read-only ID";
|
||||||
|
if(!data.writeId || data.writeId.length == 0)
|
||||||
|
throw "Invalid write-only ID";
|
||||||
|
if(data.id == data.writeId)
|
||||||
|
throw "Read-only and write-only ID cannot be the same.";
|
||||||
|
|
||||||
|
return Promise.all([
|
||||||
|
this.padIdExists(data.id).then((exists) => {
|
||||||
|
if(exists)
|
||||||
|
throw "ID '" + data.id + "' is already taken.";
|
||||||
|
}),
|
||||||
|
this.padIdExists(data.writeId).then((exists) => {
|
||||||
|
if(exists)
|
||||||
|
throw "ID '" + data.writeId + "' is already taken.";
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
|
||||||
|
create: (validate) => {
|
||||||
|
return this._conn.model("Pad").create(data);
|
||||||
|
},
|
||||||
|
|
||||||
|
types: (create) => {
|
||||||
|
return Promise.all(this.DEFAULT_TYPES.map((it) => {
|
||||||
|
return this.createType(data.id, it);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
return res.create;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updatePadData(padId, data) {
|
||||||
|
return utils.promiseAuto({
|
||||||
|
oldData: this.getPadData(padId),
|
||||||
|
|
||||||
|
validateRead: () => {
|
||||||
|
if(data.id != null && data.id != padId && data.id.length == 0)
|
||||||
|
throw "Invalid read-only ID";
|
||||||
|
|
||||||
|
var existsPromises = [ ];
|
||||||
|
|
||||||
|
if(data.id != null && data.id != padId) {
|
||||||
|
return this.padIdExists(data.id).then((exists) => {
|
||||||
|
if(exists)
|
||||||
|
throw "ID '" + data.id + "' is already taken.";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
validateWrite: (oldData) => {
|
||||||
|
if(data.writeId != null && data.writeId != oldData.writeId) {
|
||||||
|
if(data.writeId.length == 0)
|
||||||
|
throw "Invalid write-only ID";
|
||||||
|
if(data.writeId == (data.id != null ? data.id : padId))
|
||||||
|
throw "Read-only and write-only ID cannot be the same.";
|
||||||
|
|
||||||
|
return this.padIdExists(data.writeId).then((exists) => {
|
||||||
|
if(exists)
|
||||||
|
throw "ID '" + data.writeId + "' is already taken.";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
update: (validateRead, validateWrite) => {
|
||||||
|
return this._conn.model("Pad").update(data, { where: { id: padId } }).then(res => {
|
||||||
|
if(res[0] == 0)
|
||||||
|
throw "Pad " + padId + " could not be found.";
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
newData: (update) => this.getPadData(data.id || padId),
|
||||||
|
|
||||||
|
/*history: (oldData, update) => {
|
||||||
|
return this.addHistoryEntry(data.id || padId, {
|
||||||
|
type: "pad",
|
||||||
|
action: "update",
|
||||||
|
objectBefore: oldData
|
||||||
|
});
|
||||||
|
}*/
|
||||||
|
}).then((res) => {
|
||||||
|
this.emit("padData", padId, res.newData);
|
||||||
|
|
||||||
|
return res.newData;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/*function copyPad(fromPadId, toPadId, callback) {
|
||||||
|
function _handleStream(stream, next, cb) {
|
||||||
|
stream.on("data", function(data) {
|
||||||
|
stream.pause();
|
||||||
|
cb(data, function() {
|
||||||
|
stream.resume();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
stream.on("error", next);
|
||||||
|
stream.on("end", next);
|
||||||
|
}
|
||||||
|
|
||||||
|
async.auto({
|
||||||
|
fromPadData : function(next) {
|
||||||
|
backend.getPadData(fromPadId, next);
|
||||||
|
},
|
||||||
|
toPadData : function(next) {
|
||||||
|
getPadData(toPadId, next);
|
||||||
|
},
|
||||||
|
padsExist : [ "fromPadData", "toPadData", function(r, next) {
|
||||||
|
if(!r.fromPadData)
|
||||||
|
return next(new Error("Pad "+fromPadId+" does not exist."));
|
||||||
|
if(!r.toPadData.writable)
|
||||||
|
return next(new Error("Destination pad is read-only."));
|
||||||
|
|
||||||
|
toPadId = r.toPadData.id;
|
||||||
|
|
||||||
|
next();
|
||||||
|
}],
|
||||||
|
copyMarkers : [ "padsExist", function(r, next) {
|
||||||
|
_handleStream(getPadMarkers(fromPadId, null), next, function(marker, cb) {
|
||||||
|
createMarker(toPadId, marker, cb);
|
||||||
|
});
|
||||||
|
}],
|
||||||
|
copyLines : [ "padsExist", function(r, next) {
|
||||||
|
_handleStream(getPadLines(fromPadId), next, function(line, cb) {
|
||||||
|
async.auto({
|
||||||
|
createLine : function(next) {
|
||||||
|
_createLine(toPadId, line, next);
|
||||||
|
},
|
||||||
|
getLinePoints : function(next) {
|
||||||
|
backend.getLinePoints(line.id, next);
|
||||||
|
},
|
||||||
|
setLinePoints : [ "createLine", "getLinePoints", function(r, next) {
|
||||||
|
_setLinePoints(toPadId, r.createLine.id, r.getLinePoints, next);
|
||||||
|
} ]
|
||||||
|
}, cb);
|
||||||
|
});
|
||||||
|
}],
|
||||||
|
copyViews : [ "padsExist", function(r, next) {
|
||||||
|
_handleStream(getViews(fromPadId), next, function(view, cb) {
|
||||||
|
createView(toPadId, view, function(err, newView) {
|
||||||
|
if(err)
|
||||||
|
return cb(err);
|
||||||
|
|
||||||
|
if(r.fromPadData.defaultView && r.fromPadData.defaultView.id == view.id && r.toPadData.defaultView == null)
|
||||||
|
updatePadData(toPadId, { defaultView: newView.id }, cb);
|
||||||
|
else
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}]
|
||||||
|
}, callback);
|
||||||
|
}*/
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,175 @@
|
||||||
|
var Sequelize = require("sequelize");
|
||||||
|
|
||||||
|
var utils = require("../utils");
|
||||||
|
|
||||||
|
module.exports = function(Database) {
|
||||||
|
Database.prototype._init.push(function() {
|
||||||
|
var Type = this._conn.define("Type", {
|
||||||
|
name: { type: Sequelize.TEXT, allowNull: false },
|
||||||
|
type: { type: Sequelize.ENUM("marker", "line"), allowNull: false },
|
||||||
|
defaultColour: { type: Sequelize.STRING(6), allowNull: true, validate: this._TYPES.validateColour },
|
||||||
|
colourFixed: { type: Sequelize.BOOLEAN, allowNull: true },
|
||||||
|
defaultSize: { type: Sequelize.INTEGER.UNSIGNED, allowNull: true, validate: { min: 15 } },
|
||||||
|
sizeFixed: { type: Sequelize.BOOLEAN, allowNull: true },
|
||||||
|
defaultSymbol: { type: Sequelize.TEXT, allowNull: true},
|
||||||
|
symbolFixed: { type: Sequelize.BOOLEAN, allowNull: true},
|
||||||
|
defaultWidth: { type: Sequelize.INTEGER.UNSIGNED, allowNull: true, validate: { min: 1 } },
|
||||||
|
widthFixed: { type: Sequelize.BOOLEAN, allowNull: true },
|
||||||
|
defaultMode: { type: Sequelize.ENUM("", "car", "bicycle", "pedestrian", "track"), allowNull: true },
|
||||||
|
modeFixed: { type: Sequelize.BOOLEAN, allowNull: true },
|
||||||
|
|
||||||
|
fields: {
|
||||||
|
type: Sequelize.TEXT,
|
||||||
|
allowNull: false,
|
||||||
|
get: function() {
|
||||||
|
return JSON.parse(this.getDataValue("fields"));
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
|
return this.setDataValue("fields", JSON.stringify(v));
|
||||||
|
},
|
||||||
|
validate: {
|
||||||
|
checkUniqueFieldName: (obj) => {
|
||||||
|
obj = JSON.parse(obj);
|
||||||
|
var fields = { };
|
||||||
|
for(var i=0; i<obj.length; i++) {
|
||||||
|
if(obj[i].name.trim().length == 0)
|
||||||
|
throw new Error("Empty field name.");
|
||||||
|
if(fields[obj[i].name])
|
||||||
|
throw new Error("field name "+obj[i].name+" is not unique.");
|
||||||
|
fields[obj[i].name] = true;
|
||||||
|
if([ "textarea", "dropdown", "checkbox", "input" ].indexOf(obj[i].type) == -1)
|
||||||
|
throw new Error("Invalid field type "+obj[i].type+" for field "+obj[i].name+".");
|
||||||
|
if(obj[i].controlColour) {
|
||||||
|
if(!obj[i].options || obj[i].options.length < 1)
|
||||||
|
throw new Error("No options specified for colour-controlling field "+obj[i].name+".");
|
||||||
|
for(var j=0; j<obj[i].options.length; j++) {
|
||||||
|
if(!obj[i].options[j].colour || !obj[i].options[j].colour.match(this._TYPES.validateColour.is))
|
||||||
|
throw new Error("Invalid colour "+obj[i].options[j].colour+" in field "+obj[i].name+".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(obj[i].controlSize) {
|
||||||
|
if(!obj[i].options || obj[i].options.length < 1)
|
||||||
|
throw new Error("No options specified for size-controlling field "+obj[i].name+".");
|
||||||
|
for(var j=0; j<obj[i].options.length; j++) {
|
||||||
|
if(!obj[i].options[j].size || !isFinite(obj[i].options[j].size) || obj[i].options[j].size < 15)
|
||||||
|
throw new Error("Invalid size "+obj[i].options[j].size+" in field "+obj[i].name+".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(obj[i].controlSymbol) {
|
||||||
|
if(!obj[i].options || obj[i].options.length < 1)
|
||||||
|
throw new Error("No options specified for icon-controlling field "+obj[i].name+".");
|
||||||
|
}
|
||||||
|
if(obj[i].controlWidth) {
|
||||||
|
if(!obj[i].options || obj[i].options.length < 1)
|
||||||
|
throw new Error("No options specified for width-controlling field "+obj[i].name+".");
|
||||||
|
for(var j=0; j<obj[i].options.length; j++) {
|
||||||
|
if(!obj[i].options[j].width || !(1*obj[i].options[j].width >= 1))
|
||||||
|
throw new Error("Invalid width "+obj[i].options[j].width+" in field "+obj[i].name+".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
validate: {
|
||||||
|
defaultValsNotNull: function() {
|
||||||
|
if(this.colourFixed && this.defaultColour == null)
|
||||||
|
throw "Fixed colour cannot be undefined.";
|
||||||
|
if(this.sizeFixed && this.defaultSize == null)
|
||||||
|
throw "Fixed size cannot be undefined.";
|
||||||
|
if(this.widthFixed && this.defaultWidth == null)
|
||||||
|
throw "Fixed width cannot be undefined.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Database.prototype._afterInit.push(function() {
|
||||||
|
this._conn.model("Type").belongsTo(this._conn.model("Pad"), this._makeNotNullForeignKey("pad", "padId"));
|
||||||
|
this._conn.model("Pad").hasMany(this._conn.model("Type"), { foreignKey: "padId" });
|
||||||
|
});
|
||||||
|
|
||||||
|
// =====================================================================================================================
|
||||||
|
|
||||||
|
utils.extend(Database.prototype, {
|
||||||
|
DEFAULT_TYPES: [
|
||||||
|
{ name: "Marker", type: "marker", fields: [ { name: "Description", type: "textarea" } ] },
|
||||||
|
{ name: "Line", type: "line", fields: [ { name: "Description", type: "textarea" } ] }
|
||||||
|
],
|
||||||
|
|
||||||
|
getTypes(padId) {
|
||||||
|
return this._getPadObjects("Type", padId);
|
||||||
|
},
|
||||||
|
|
||||||
|
getType(padId, typeId) {
|
||||||
|
return this._conn.model("Type").findOne({ where: { id: typeId, padId: padId } }).then(res => {
|
||||||
|
if(res == null)
|
||||||
|
throw new Error("Type " + typeId + " of pad " + padId + " could not be found.");
|
||||||
|
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
createType(padId, data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(data.name == null || data.name.trim().length == 0)
|
||||||
|
throw "No name provided.";
|
||||||
|
|
||||||
|
return this._createPadObject("Type", padId, data);
|
||||||
|
}).then((data) => {
|
||||||
|
this.emit("type", data.padId, data);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateType(padId, typeId, data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(data.name == null || data.name.trim().length == 0)
|
||||||
|
throw "No name provided.";
|
||||||
|
|
||||||
|
return this._updatePadObject("Type", padId, typeId, data);
|
||||||
|
}).then((data) => {
|
||||||
|
this.emit("type", data.padId, data);
|
||||||
|
|
||||||
|
return this._updateObjectStyles(data.type == "line" ? this.getPadLinesByType(data.padId, typeId) : this.getPadMarkersByType(data.padId, typeId), data.type == "line").then(() => data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
_optionsToObj(options, idx) {
|
||||||
|
var ret = { };
|
||||||
|
if(options) {
|
||||||
|
for(var i=0; i<options.length; i++) {
|
||||||
|
ret[options[i].key] = options[i][idx];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
|
||||||
|
isTypeUsed(padId, typeId) {
|
||||||
|
return Promise.all([
|
||||||
|
this._conn.model("Marker").findOne({ where: { padId: padId, typeId: typeId } }),
|
||||||
|
this._conn.model("Line").findOne({ where: { padId: padId, typeId: typeId } })
|
||||||
|
]).then(res => {
|
||||||
|
return !!res[0] || !!res[1];
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteType(padId, typeId) {
|
||||||
|
return utils.promiseAuto({
|
||||||
|
isUsed: this.isTypeUsed(padId, typeId),
|
||||||
|
del: (isUsed) => {
|
||||||
|
if(isUsed)
|
||||||
|
throw "This type is in use.";
|
||||||
|
|
||||||
|
return this._deletePadObject("Type", padId, typeId);
|
||||||
|
}
|
||||||
|
}).then((res) => {
|
||||||
|
this.emit("deleteType", padId, { id: res.del.id });
|
||||||
|
|
||||||
|
return res.del;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,78 @@
|
||||||
|
var Sequelize = require("sequelize");
|
||||||
|
|
||||||
|
var utils = require("../utils");
|
||||||
|
|
||||||
|
module.exports = function(Database) {
|
||||||
|
Database.prototype._init.push(function() {
|
||||||
|
this._conn.define("View", {
|
||||||
|
name : { type: Sequelize.TEXT, allowNull: false },
|
||||||
|
baseLayer : { type: Sequelize.TEXT, allowNull: false },
|
||||||
|
layers : {
|
||||||
|
type: Sequelize.TEXT,
|
||||||
|
allowNull: false,
|
||||||
|
get: function() {
|
||||||
|
return JSON.parse(this.getDataValue("layers"));
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
|
this.setDataValue("layers", JSON.stringify(v));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
top : this._TYPES.lat,
|
||||||
|
bottom : this._TYPES.lat,
|
||||||
|
left : this._TYPES.lon,
|
||||||
|
right : this._TYPES.lon,
|
||||||
|
filter: { type: Sequelize.TEXT, allowNull: true }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
Database.prototype._afterInit.push(function() {
|
||||||
|
this._conn.model("View").belongsTo(this._conn.model("Pad"), this._makeNotNullForeignKey("pad", "padId"));
|
||||||
|
this._conn.model("Pad").hasMany(this._conn.model("View"), { foreignKey: "padId" });
|
||||||
|
});
|
||||||
|
|
||||||
|
// =====================================================================================================================
|
||||||
|
|
||||||
|
utils.extend(Database.prototype, {
|
||||||
|
getViews(padId) {
|
||||||
|
return this._getPadObjects("View", padId);
|
||||||
|
},
|
||||||
|
|
||||||
|
createView(padId, data) {
|
||||||
|
return utils.promiseAuto({
|
||||||
|
create: () => {
|
||||||
|
if(data.name == null || data.name.trim().length == 0)
|
||||||
|
throw "No name provided.";
|
||||||
|
|
||||||
|
return this._createPadObject("View", padId, data);
|
||||||
|
},
|
||||||
|
|
||||||
|
/*history: (create) => {
|
||||||
|
return this.addHistoryEntry(padId, {
|
||||||
|
type: "view",
|
||||||
|
action: "create"
|
||||||
|
});
|
||||||
|
}*/
|
||||||
|
}).then((res) => {
|
||||||
|
this.emit("view", padId, res.create);
|
||||||
|
|
||||||
|
return res.create;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateView(padId, viewId, data) {
|
||||||
|
return this._updatePadObject("View", padId, viewId, data).then((newData) => {
|
||||||
|
this.emit("view", padId, newData);
|
||||||
|
|
||||||
|
return newData;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteView(padId, viewId) {
|
||||||
|
return this._deletePadObject("View", padId, viewId).then((data) => {
|
||||||
|
this.emit("deleteView", padId, { id: data.id });
|
||||||
|
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,666 +0,0 @@
|
||||||
var Sequelize = require("sequelize");
|
|
||||||
var config = require("../config");
|
|
||||||
var utils = require("./utils");
|
|
||||||
var Promise = require("promise");
|
|
||||||
|
|
||||||
var conn = new Sequelize(config.db.database, config.db.user, config.db.password, {
|
|
||||||
dialect: config.db.type,
|
|
||||||
host: config.db.host,
|
|
||||||
port: config.db.port,
|
|
||||||
define: {
|
|
||||||
timestamps: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/*********************/
|
|
||||||
/* Types and Helpers */
|
|
||||||
/*********************/
|
|
||||||
|
|
||||||
function getLatType() {
|
|
||||||
return {
|
|
||||||
type: Sequelize.FLOAT(9, 6),
|
|
||||||
allowNull: false,
|
|
||||||
validate: {
|
|
||||||
min: -90,
|
|
||||||
max: 90
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLonType() {
|
|
||||||
return {
|
|
||||||
type: Sequelize.FLOAT(9, 6),
|
|
||||||
allowNull: false,
|
|
||||||
validate: {
|
|
||||||
min: -180,
|
|
||||||
max: 180
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var validateColour = { is: /^[a-fA-F0-9]{3}([a-fA-F0-9]{3})?$/ };
|
|
||||||
|
|
||||||
var dataDefinition = {
|
|
||||||
"name" : { type: Sequelize.TEXT, allowNull: false },
|
|
||||||
"value" : { type: Sequelize.TEXT, allowNull: false }
|
|
||||||
};
|
|
||||||
|
|
||||||
function _makeNotNullForeignKey(type, field, error) {
|
|
||||||
return {
|
|
||||||
as: type,
|
|
||||||
onDelete: error ? "RESTRICT" : "CASCADE",
|
|
||||||
foreignKey: { name: field, allowNull: false }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**********/
|
|
||||||
/* Tables */
|
|
||||||
/**********/
|
|
||||||
|
|
||||||
/* Pads */
|
|
||||||
|
|
||||||
var Pad = conn.define("Pad", {
|
|
||||||
id : { type: Sequelize.STRING, allowNull: false, primaryKey: true, validate: { is: /^.+$/ } },
|
|
||||||
name: { type: Sequelize.TEXT, allowNull: true, get: function() { return this.getDataValue("name") || "New FacilMap"; } },
|
|
||||||
writeId: { type: Sequelize.STRING, allowNull: false, validate: { is: /^.+$/ } }
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
/* Markers */
|
|
||||||
|
|
||||||
var Marker = conn.define("Marker", {
|
|
||||||
"lat" : getLatType(),
|
|
||||||
"lon" : getLonType(),
|
|
||||||
name : { type: Sequelize.TEXT, allowNull: true, get: function() { return this.getDataValue("name") || "Untitled marker"; } },
|
|
||||||
colour : { type: Sequelize.STRING(6), allowNull: false, defaultValue: "ff0000", validate: validateColour },
|
|
||||||
size : { type: Sequelize.INTEGER.UNSIGNED, allowNull: false, defaultValue: 25, validate: { min: 15 } },
|
|
||||||
symbol : { type: Sequelize.TEXT, allogNull: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
Pad.hasMany(Marker, { foreignKey: "padId" });
|
|
||||||
Marker.belongsTo(Pad, _makeNotNullForeignKey("pad", "padId"));
|
|
||||||
|
|
||||||
var MarkerData = conn.define("MarkerData", dataDefinition);
|
|
||||||
|
|
||||||
MarkerData.belongsTo(Marker, _makeNotNullForeignKey("marker", "markerId"));
|
|
||||||
Marker.hasMany(MarkerData, { foreignKey: "markerId" });
|
|
||||||
|
|
||||||
|
|
||||||
/* Lines */
|
|
||||||
|
|
||||||
var Line = conn.define("Line", {
|
|
||||||
routePoints : {
|
|
||||||
type: Sequelize.TEXT,
|
|
||||||
allowNull: false,
|
|
||||||
get: function() {
|
|
||||||
var routePoints = this.getDataValue("routePoints");
|
|
||||||
return routePoints != null ? JSON.parse(routePoints) : routePoints;
|
|
||||||
},
|
|
||||||
set: function(v) {
|
|
||||||
for(var i=0; i<v.length; i++) {
|
|
||||||
v[i].lat = 1*v[i].lat.toFixed(6);
|
|
||||||
v[i].lon = 1*v[i].lon.toFixed(6);
|
|
||||||
}
|
|
||||||
this.setDataValue("routePoints", JSON.stringify(v));
|
|
||||||
},
|
|
||||||
validate: {
|
|
||||||
minTwo: function(val) {
|
|
||||||
var routePoints = JSON.parse(val);
|
|
||||||
if(!Array.isArray(routePoints))
|
|
||||||
throw new Error("routePoints is not an array");
|
|
||||||
if(routePoints.length < 2)
|
|
||||||
throw new Error("A line cannot have less than two route points.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mode : { type: Sequelize.ENUM("", "car", "bicycle", "pedestrian", "track"), allowNull: false, defaultValue: "" },
|
|
||||||
colour : { type: Sequelize.STRING(6), allowNull: false, defaultValue: "0000ff", validate: validateColour },
|
|
||||||
width : { type: Sequelize.INTEGER.UNSIGNED, allowNull: false, defaultValue: 4, validate: { min: 1 } },
|
|
||||||
name : { type: Sequelize.TEXT, allowNull: true, get: function() { return this.getDataValue("name") || "Untitled line"; } },
|
|
||||||
distance : { type: Sequelize.FLOAT(24, 2).UNSIGNED, allowNull: true },
|
|
||||||
time : { type: Sequelize.INTEGER.UNSIGNED, allowNull: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
Pad.hasMany(Line, { foreignKey: "padId" });
|
|
||||||
Line.belongsTo(Pad, _makeNotNullForeignKey("pad", "padId"));
|
|
||||||
|
|
||||||
var LinePoint = conn.define("LinePoint", {
|
|
||||||
lat: getLatType(),
|
|
||||||
lon: getLonType(),
|
|
||||||
zoom: { type: Sequelize.INTEGER.UNSIGNED, allowNull: false, validate: { min: 1, max: 20 } },
|
|
||||||
idx: { type: Sequelize.INTEGER.UNSIGNED, allowNull: false }
|
|
||||||
});
|
|
||||||
|
|
||||||
LinePoint.belongsTo(Line, _makeNotNullForeignKey("line", "lineId"));
|
|
||||||
Line.hasMany(LinePoint, { foreignKey: "lineId" });
|
|
||||||
|
|
||||||
var LineData = conn.define("LineData", dataDefinition);
|
|
||||||
|
|
||||||
LineData.belongsTo(Line, _makeNotNullForeignKey("line", "lineId"));
|
|
||||||
Line.hasMany(LineData, { foreignKey: "lineId" });
|
|
||||||
|
|
||||||
|
|
||||||
/* Views */
|
|
||||||
|
|
||||||
var View = conn.define("View", {
|
|
||||||
name : { type: Sequelize.TEXT, allowNull: false },
|
|
||||||
baseLayer : { type: Sequelize.TEXT, allowNull: false },
|
|
||||||
layers : {
|
|
||||||
type: Sequelize.TEXT,
|
|
||||||
allowNull: false,
|
|
||||||
get: function() {
|
|
||||||
return JSON.parse(this.getDataValue("layers"));
|
|
||||||
},
|
|
||||||
set: function(v) {
|
|
||||||
this.setDataValue("layers", JSON.stringify(v));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
top : getLatType(),
|
|
||||||
bottom : getLatType(),
|
|
||||||
left : getLonType(),
|
|
||||||
right : getLonType(),
|
|
||||||
filter: { type: Sequelize.TEXT, allowNull: true }
|
|
||||||
});
|
|
||||||
|
|
||||||
Pad.hasMany(View, { foreignKey: "padId" });
|
|
||||||
View.belongsTo(Pad, _makeNotNullForeignKey("pad", "padId"));
|
|
||||||
|
|
||||||
Pad.belongsTo(View, { as: "defaultView", foreignKey: "defaultViewId", constraints: false });
|
|
||||||
|
|
||||||
|
|
||||||
/* Types */
|
|
||||||
|
|
||||||
var Type = conn.define("Type", {
|
|
||||||
name: { type: Sequelize.TEXT, allowNull: false },
|
|
||||||
type: { type: Sequelize.ENUM("marker", "line"), allowNull: false },
|
|
||||||
defaultColour: { type: Sequelize.STRING(6), allowNull: true, validate: validateColour },
|
|
||||||
colourFixed: { type: Sequelize.BOOLEAN, allowNull: true },
|
|
||||||
defaultSize: { type: Sequelize.INTEGER.UNSIGNED, allowNull: true, validate: { min: 15 } },
|
|
||||||
sizeFixed: { type: Sequelize.BOOLEAN, allowNull: true },
|
|
||||||
defaultSymbol: { type: Sequelize.TEXT, allowNull: true},
|
|
||||||
symbolFixed: { type: Sequelize.BOOLEAN, allowNull: true},
|
|
||||||
defaultWidth: { type: Sequelize.INTEGER.UNSIGNED, allowNull: true, validate: { min: 1 } },
|
|
||||||
widthFixed: { type: Sequelize.BOOLEAN, allowNull: true },
|
|
||||||
defaultMode: { type: Sequelize.ENUM("", "car", "bicycle", "pedestrian", "track"), allowNull: true },
|
|
||||||
modeFixed: { type: Sequelize.BOOLEAN, allowNull: true },
|
|
||||||
|
|
||||||
fields: {
|
|
||||||
type: Sequelize.TEXT,
|
|
||||||
allowNull: false,
|
|
||||||
get: function() {
|
|
||||||
return JSON.parse(this.getDataValue("fields"));
|
|
||||||
},
|
|
||||||
set: function(v) {
|
|
||||||
return this.setDataValue("fields", JSON.stringify(v));
|
|
||||||
},
|
|
||||||
validate: {
|
|
||||||
checkUniqueFieldName: function(obj) {
|
|
||||||
obj = JSON.parse(obj);
|
|
||||||
var fields = { };
|
|
||||||
for(var i=0; i<obj.length; i++) {
|
|
||||||
if(obj[i].name.trim().length == 0)
|
|
||||||
throw new Error("Empty field name.");
|
|
||||||
if(fields[obj[i].name])
|
|
||||||
throw new Error("field name "+obj[i].name+" is not unique.");
|
|
||||||
fields[obj[i].name] = true;
|
|
||||||
if([ "textarea", "dropdown", "checkbox", "input" ].indexOf(obj[i].type) == -1)
|
|
||||||
throw new Error("Invalid field type "+obj[i].type+" for field "+obj[i].name+".");
|
|
||||||
if(obj[i].controlColour) {
|
|
||||||
if(!obj[i].options || obj[i].options.length < 1)
|
|
||||||
throw new Error("No options specified for colour-controlling field "+obj[i].name+".");
|
|
||||||
for(var j=0; j<obj[i].options.length; j++) {
|
|
||||||
if(!obj[i].options[j].colour || !obj[i].options[j].colour.match(validateColour.is))
|
|
||||||
throw new Error("Invalid colour "+obj[i].options[j].colour+" in field "+obj[i].name+".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(obj[i].controlSize) {
|
|
||||||
if(!obj[i].options || obj[i].options.length < 1)
|
|
||||||
throw new Error("No options specified for size-controlling field "+obj[i].name+".");
|
|
||||||
for(var j=0; j<obj[i].options.length; j++) {
|
|
||||||
if(!obj[i].options[j].size || !isFinite(obj[i].options[j].size) || obj[i].options[j].size < 15)
|
|
||||||
throw new Error("Invalid size "+obj[i].options[j].size+" in field "+obj[i].name+".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(obj[i].controlSymbol) {
|
|
||||||
if(!obj[i].options || obj[i].options.length < 1)
|
|
||||||
throw new Error("No options specified for icon-controlling field "+obj[i].name+".");
|
|
||||||
}
|
|
||||||
if(obj[i].controlWidth) {
|
|
||||||
if(!obj[i].options || obj[i].options.length < 1)
|
|
||||||
throw new Error("No options specified for width-controlling field "+obj[i].name+".");
|
|
||||||
for(var j=0; j<obj[i].options.length; j++) {
|
|
||||||
if(!obj[i].options[j].width || !(1*obj[i].options[j].width >= 1))
|
|
||||||
throw new Error("Invalid width "+obj[i].options[j].width+" in field "+obj[i].name+".");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
validate: {
|
|
||||||
defaultValsNotNull: function() {
|
|
||||||
if(this.colourFixed && this.defaultColour == null)
|
|
||||||
throw "Fixed colour cannot be undefined.";
|
|
||||||
if(this.sizeFixed && this.defaultSize == null)
|
|
||||||
throw "Fixed size cannot be undefined.";
|
|
||||||
if(this.widthFixed && this.defaultWidth == null)
|
|
||||||
throw "Fixed width cannot be undefined.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Pad.hasMany(Type, { foreignKey: "padId" });
|
|
||||||
Type.belongsTo(Pad, _makeNotNullForeignKey("pad", "padId"));
|
|
||||||
|
|
||||||
Marker.belongsTo(Type, _makeNotNullForeignKey("type", "typeId", true));
|
|
||||||
Line.belongsTo(Type, _makeNotNullForeignKey("type", "typeId", true));
|
|
||||||
|
|
||||||
|
|
||||||
function connect(force) {
|
|
||||||
conn.authenticate().then(function() {
|
|
||||||
return conn.sync({ force: !!force });
|
|
||||||
}).then(function() {
|
|
||||||
// Migrations
|
|
||||||
|
|
||||||
var queryInterface = conn.getQueryInterface();
|
|
||||||
return Promise.all([
|
|
||||||
queryInterface.describeTable('Lines').then(function(attributes) {
|
|
||||||
var promises = [ ];
|
|
||||||
|
|
||||||
// Rename Line.points to Line.routePoints
|
|
||||||
if(attributes.points) {
|
|
||||||
promises.push(queryInterface.renameColumn('Lines', 'points', 'routePoints'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Change routing type "shortest" / "fastest" to "car", add type "track"
|
|
||||||
if(attributes.mode.type.indexOf("shortest") != -1) {
|
|
||||||
promises.push(
|
|
||||||
Promise.resolve().then(function() {
|
|
||||||
return queryInterface.changeColumn('Lines', 'mode', {
|
|
||||||
type: Sequelize.ENUM("", "shortest", "fastest", "car", "bicycle", "pedestrian"), allowNull: false, defaultValue: ""
|
|
||||||
});
|
|
||||||
}).then(function() {
|
|
||||||
return Line.update({ mode: "car" }, { where: { mode: { $in: [ "fastest", "shortest" ] } } });
|
|
||||||
}).then(function() {
|
|
||||||
return queryInterface.changeColumn('Lines', 'mode', {
|
|
||||||
type: Sequelize.ENUM("", "car", "bicycle", "pedestrian", "track"), allowNull: false, defaultValue: ""
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(promises);
|
|
||||||
}),
|
|
||||||
queryInterface.describeTable('Markers').then(function(attributes) {
|
|
||||||
var promises = [ ];
|
|
||||||
|
|
||||||
// Add size and symbol columns
|
|
||||||
if(!attributes.size)
|
|
||||||
promises.push(queryInterface.addColumn('Markers', 'size', Marker.attributes.size));
|
|
||||||
if(!attributes.symbol)
|
|
||||||
promises.push(queryInterface.addColumn('Markers', 'symbol', Marker.attributes.symbol));
|
|
||||||
|
|
||||||
return Promise.all(promises);
|
|
||||||
}),
|
|
||||||
queryInterface.describeTable('Types').then(function(attributes) {
|
|
||||||
return Promise.all([ 'defaultColour', 'colourFixed', 'defaultSize', 'sizeFixed', 'defaultSymbol', 'symbolFixed', 'defaultWidth', 'widthFixed', 'defaultMode', 'modeFixed' ].map(function(col) {
|
|
||||||
if(!attributes[col])
|
|
||||||
return queryInterface.addColumn('Types', col, Type.attributes[col]);
|
|
||||||
}));
|
|
||||||
}),
|
|
||||||
queryInterface.describeTable('Views').then(function(attributes) {
|
|
||||||
if(!attributes.filter)
|
|
||||||
return queryInterface.addColumn('Views', 'filter', View.attributes.filter);
|
|
||||||
})
|
|
||||||
].concat([ 'Pads', 'Markers', 'Lines' ].map(function(table) {
|
|
||||||
// allow null on Pad.name, Marker.name, Line.name
|
|
||||||
return queryInterface.describeTable(table).then(function(attributes) {
|
|
||||||
if(!attributes.name.allowNull)
|
|
||||||
return queryInterface.changeColumn(table, 'name', { type: Sequelize.TEXT, allowNull: true });
|
|
||||||
});
|
|
||||||
})));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function padIdExists(padId) {
|
|
||||||
return Pad.count({ where: { $or: [ { id: padId }, { writeId: padId } ] } }).then(function(num) {
|
|
||||||
return num > 0;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPadData(padId) {
|
|
||||||
return Pad.findOne({ where: { id: padId }, include: [ { model: View, as: "defaultView" } ]});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPadDataByWriteId(writeId) {
|
|
||||||
return Pad.findOne({ where: { writeId: writeId }, include: [ { model: View, as: "defaultView" } ] });
|
|
||||||
}
|
|
||||||
|
|
||||||
function createPad(data) {
|
|
||||||
return Pad.create(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updatePadData(padId, data) {
|
|
||||||
return Pad.update(data, { where: { id: padId } }).then(function() {
|
|
||||||
return getPadData(data.id || padId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getPadObjects(type, padId, condition) {
|
|
||||||
var ret = new utils.ArrayStream();
|
|
||||||
|
|
||||||
Pad.build({ id: padId })["get"+type+"s"](condition).then(function(objs) {
|
|
||||||
objs.forEach(function(it) {
|
|
||||||
if(it[type+"Data"] != null) {
|
|
||||||
it.data = _dataFromArr(it[type+"Data"]);
|
|
||||||
it.setDataValue("data", it.data); // For JSON.stringify()
|
|
||||||
it.setDataValue(type+"Data", undefined);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
ret.receiveArray(null, objs);
|
|
||||||
}, function(err) {
|
|
||||||
ret.receiveArray(err);
|
|
||||||
});
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _createPadObject(type, padId, data) {
|
|
||||||
var obj = conn.model(type).build(data);
|
|
||||||
obj.padId = padId;
|
|
||||||
return obj.save();
|
|
||||||
}
|
|
||||||
|
|
||||||
function _createPadObjectWithData(type, padId, data) {
|
|
||||||
return _createPadObject(type, padId, data).then(function(obj) {
|
|
||||||
if(data.data != null) {
|
|
||||||
obj.data = data.data;
|
|
||||||
obj.setDataValue("data", obj.data); // For JSON.stringify()
|
|
||||||
return _setObjectData(type, obj.id, data.data).then(function() {
|
|
||||||
return obj;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
obj.data = { };
|
|
||||||
obj.setDataValue("data", obj.data); // For JSON.stringify()
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _updatePadObject(type, objId, data) {
|
|
||||||
return conn.model(type).update(data, { where: { id: objId } }).then(function() {
|
|
||||||
return conn.model(type).findById(objId);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _updatePadObjectWithData(type, objId, data) {
|
|
||||||
return Promise.all([
|
|
||||||
_updatePadObject(type, objId, data),
|
|
||||||
data.data != null ? _setObjectData(type, objId, data.data) : _getObjectData(type, objId)
|
|
||||||
]).then(function(results) {
|
|
||||||
var obj = results[0];
|
|
||||||
obj.data = (data.data != null ? data.data : results[1]);
|
|
||||||
obj.setDataValue("data", obj.data); // For JSON.stringify()
|
|
||||||
return obj;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _deletePadObject(type, objId) {
|
|
||||||
return conn.model(type).findById(objId).then(function(obj) {
|
|
||||||
return obj.destroy().then(function() {
|
|
||||||
return obj;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _deletePadObjectWithData(type, objId) {
|
|
||||||
return _setObjectData(type, objId, { }).then(function() {
|
|
||||||
return _deletePadObject(type, objId); // Return the object
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _dataToArr(data, extend) {
|
|
||||||
var dataArr = [ ];
|
|
||||||
for(var i in data)
|
|
||||||
dataArr.push(utils.extend({ name: i, value: data[i] }, extend));
|
|
||||||
return dataArr;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _dataFromArr(dataArr) {
|
|
||||||
var data = { };
|
|
||||||
for(var i=0; i<dataArr.length; i++)
|
|
||||||
data[dataArr[i].name] = dataArr[i].value;
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
function _getObjectData(type, objId) {
|
|
||||||
var filter = { };
|
|
||||||
filter[type.toLowerCase()+"Id"] = objId;
|
|
||||||
|
|
||||||
return conn.model(type+"Data").findAll({ where: filter}).then(function(dataArr) {
|
|
||||||
return _dataFromArr(dataArr);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _setObjectData(type, objId, data) {
|
|
||||||
var model = conn.model(type+"Data");
|
|
||||||
var idObj = { };
|
|
||||||
idObj[type.toLowerCase()+"Id"] = objId;
|
|
||||||
|
|
||||||
return model.destroy({ where: idObj}).then(function() {
|
|
||||||
return model.bulkCreate(_dataToArr(data, idObj));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getViews(padId) {
|
|
||||||
return _getPadObjects("View", padId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createView(padId, data) {
|
|
||||||
return _createPadObject("View", padId, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateView(viewId, data) {
|
|
||||||
return _updatePadObject("View", viewId, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteView(viewId) {
|
|
||||||
return _deletePadObject("View", viewId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getType(typeId) {
|
|
||||||
return Type.findById(typeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTypes(padId) {
|
|
||||||
return _getPadObjects("Type", padId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function createType(padId, data) {
|
|
||||||
return _createPadObject("Type", padId, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateType(typeId, data) {
|
|
||||||
return _updatePadObject("Type", typeId, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteType(typeId) {
|
|
||||||
return _deletePadObject("Type", typeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function isTypeUsed(typeId) {
|
|
||||||
return Promise.all([
|
|
||||||
Marker.findOne({ where: { typeId: typeId } }),
|
|
||||||
Line.findOne({ where: { typeId: typeId } })
|
|
||||||
]).then(function(res) {
|
|
||||||
return res[0] != null || res[1] != null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPadMarkers(padId, bbox) {
|
|
||||||
return _getPadObjects("Marker", padId, { where: _makeBboxCondition(bbox), include: [ MarkerData ] });
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPadMarkersByType(padId, typeId) {
|
|
||||||
return _getPadObjects("Marker", padId, { where: { typeId: typeId }, include: [ MarkerData ] });
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMarker(padId, data) {
|
|
||||||
return _createPadObjectWithData("Marker", padId, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMarker(markerId, data) {
|
|
||||||
return _updatePadObjectWithData("Marker", markerId, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteMarker(markerId) {
|
|
||||||
return _deletePadObjectWithData("Marker", markerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPadLines(padId, fields) {
|
|
||||||
var cond = { include: [ LineData ] };
|
|
||||||
if(fields)
|
|
||||||
cond.attributes = (typeof fields == "string" ? fields.split(/\s+/) : fields);
|
|
||||||
|
|
||||||
return _getPadObjects("Line", padId, cond);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPadLinesByType(padId, typeId) {
|
|
||||||
return _getPadObjects("Line", padId, { where: { typeId: typeId }, include: [ LineData ] });
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLineTemplate(data) {
|
|
||||||
var line = JSON.parse(JSON.stringify(Line.build(data)));
|
|
||||||
line.data = data.data || { };
|
|
||||||
return Promise.resolve(line);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLine(lineId) {
|
|
||||||
return Line.findOne({ where: { id: lineId }, include: [ LineData ] });
|
|
||||||
}
|
|
||||||
|
|
||||||
function createLine(padId, data) {
|
|
||||||
return _createPadObjectWithData("Line", padId, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateLine(lineId, data) {
|
|
||||||
return _updatePadObjectWithData("Line", lineId, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteLine(lineId) {
|
|
||||||
return _deletePadObjectWithData("Line", lineId);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLinePoints(lineId) {
|
|
||||||
return Line.build({ id: lineId }).getLinePoints();
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLinePointsByBbox(lineId, bboxWithZoom) {
|
|
||||||
return Line.build({ id: lineId }).getLinePoints({
|
|
||||||
where: Sequelize.and(_makeBboxCondition(bboxWithZoom), bboxWithZoom ? { zoom: { lte: bboxWithZoom.zoom } } : null),
|
|
||||||
attributes: [ "idx" ],
|
|
||||||
order: "idx"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getLinePointsByIdx(lineId, indexes) {
|
|
||||||
return Line.build({ id: lineId }).getLinePoints({
|
|
||||||
where: { idx: indexes },
|
|
||||||
attributes: [ "lon", "lat", "idx" ],
|
|
||||||
order: "idx"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function setLinePoints(lineId, trackPoints) {
|
|
||||||
return LinePoint.destroy({ where: { lineId: lineId } }).then(function() {
|
|
||||||
var create = [ ];
|
|
||||||
for(var i=0; i<trackPoints.length; i++) {
|
|
||||||
create.push(utils.extend({ }, trackPoints[i], { lineId: lineId }));
|
|
||||||
}
|
|
||||||
|
|
||||||
return LinePoint.bulkCreate(create);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function _makeBboxCondition(bbox, prefix) {
|
|
||||||
if(!bbox)
|
|
||||||
return { };
|
|
||||||
|
|
||||||
prefix = prefix || "";
|
|
||||||
|
|
||||||
function cond(key, value) {
|
|
||||||
var ret = { };
|
|
||||||
ret[prefix+key] = value;
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
var conditions = [ ];
|
|
||||||
conditions.push(cond("lat", { lte: bbox.top, gte: bbox.bottom }));
|
|
||||||
|
|
||||||
if(bbox.right < bbox.left) // Bbox spans over lon=180
|
|
||||||
conditions.push(Sequelize.or(cond("lon", { gte: bbox.left }), cond("lon", { lte: bbox.right })));
|
|
||||||
else
|
|
||||||
conditions.push(cond("lon", { gte: bbox.left, lte: bbox.right }));
|
|
||||||
|
|
||||||
if(bbox.except) {
|
|
||||||
var exceptConditions = [ ];
|
|
||||||
exceptConditions.push(Sequelize.or(cond("lat", { gt: bbox.except.top }), cond("lat", { lt: bbox.except.bottom })));
|
|
||||||
|
|
||||||
if(bbox.except.right < bbox.except.left)
|
|
||||||
exceptConditions.push(cond("lon", { lt: bbox.except.left, gt: bbox.except.right }));
|
|
||||||
else
|
|
||||||
exceptConditions.push(Sequelize.or(cond("lon", { lt: bbox.except.left }), cond("lon", { gt: bbox.except.right })));
|
|
||||||
conditions.push(Sequelize.or.apply(Sequelize, exceptConditions));
|
|
||||||
}
|
|
||||||
|
|
||||||
return Sequelize.and.apply(Sequelize, conditions);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
connect : connect,
|
|
||||||
padIdExists : padIdExists,
|
|
||||||
getPadData : getPadData,
|
|
||||||
getPadDataByWriteId : getPadDataByWriteId,
|
|
||||||
createPad : createPad,
|
|
||||||
updatePadData : updatePadData,
|
|
||||||
getViews : getViews,
|
|
||||||
createView : createView,
|
|
||||||
updateView : updateView,
|
|
||||||
deleteView : deleteView,
|
|
||||||
getType : getType,
|
|
||||||
getTypes : getTypes,
|
|
||||||
createType : createType,
|
|
||||||
updateType : updateType,
|
|
||||||
deleteType : deleteType,
|
|
||||||
isTypeUsed : isTypeUsed,
|
|
||||||
getPadMarkers : getPadMarkers,
|
|
||||||
getPadMarkersByType : getPadMarkersByType,
|
|
||||||
createMarker : createMarker,
|
|
||||||
updateMarker : updateMarker,
|
|
||||||
deleteMarker : deleteMarker,
|
|
||||||
getPadLines : getPadLines,
|
|
||||||
getPadLinesByType : getPadLinesByType,
|
|
||||||
getLine : getLine,
|
|
||||||
getLineTemplate : getLineTemplate,
|
|
||||||
createLine : createLine,
|
|
||||||
updateLine : updateLine,
|
|
||||||
deleteLine : deleteLine,
|
|
||||||
getLinePoints : getLinePoints,
|
|
||||||
getLinePointsByBbox : getLinePointsByBbox,
|
|
||||||
getLinePointsByIdx : getLinePointsByIdx,
|
|
||||||
setLinePoints : setLinePoints,
|
|
||||||
_models : {
|
|
||||||
Pad: Pad,
|
|
||||||
Marker: Marker,
|
|
||||||
MarkerData: MarkerData,
|
|
||||||
Line: Line,
|
|
||||||
LineData: LineData,
|
|
||||||
LinePoint: LinePoint,
|
|
||||||
View: View,
|
|
||||||
Type: Type
|
|
||||||
},
|
|
||||||
_conn : conn
|
|
||||||
};
|
|
|
@ -1,273 +0,0 @@
|
||||||
var readline = require("readline");
|
|
||||||
var mongoose = require("mongoose");
|
|
||||||
var db = require("./databaseBackendSequelize");
|
|
||||||
var db2 = require("./database");
|
|
||||||
var utils = require("./utils");
|
|
||||||
var async = require("async");
|
|
||||||
|
|
||||||
|
|
||||||
var OLD_MARKER_COLOURS = { blue: "8da8f6", green: "90ee90", gold: "ffd700", red: "ff0000" };
|
|
||||||
|
|
||||||
var ObjectId = mongoose.Schema.Types.ObjectId;
|
|
||||||
|
|
||||||
var positionType = {
|
|
||||||
lon: Number,
|
|
||||||
lat: Number
|
|
||||||
};
|
|
||||||
|
|
||||||
var bboxType = {
|
|
||||||
top: Number,
|
|
||||||
right: Number,
|
|
||||||
bottom: Number,
|
|
||||||
left: Number
|
|
||||||
};
|
|
||||||
|
|
||||||
var markerSchema = mongoose.Schema({
|
|
||||||
_pad : { type: String, ref: "Pad" },
|
|
||||||
position : positionType,
|
|
||||||
name : { type: String, default: "Untitled marker" },
|
|
||||||
description : String,
|
|
||||||
colour : { type: String, default: "ff0000" }
|
|
||||||
});
|
|
||||||
|
|
||||||
var lineSchema = mongoose.Schema({
|
|
||||||
_pad : { type: String, ref: "Pad" },
|
|
||||||
points : [positionType],
|
|
||||||
mode : { type: String, default: "" },
|
|
||||||
colour : { type: String, default: "0000ff" },
|
|
||||||
width : { type: Number, default: 4 },
|
|
||||||
description : String,
|
|
||||||
name : { type: String, default: "Untitled line" },
|
|
||||||
distance : Number,
|
|
||||||
time : Number
|
|
||||||
});
|
|
||||||
|
|
||||||
var linePointsSchema = mongoose.Schema(utils.extend({ }, positionType, {
|
|
||||||
zoom : Number,
|
|
||||||
idx : Number,
|
|
||||||
_line : { type: ObjectId, ref: "Line" }
|
|
||||||
}));
|
|
||||||
|
|
||||||
var viewSchema = mongoose.Schema({
|
|
||||||
_pad : { type: String, ref: "Pad" },
|
|
||||||
name : String,
|
|
||||||
baseLayer : String,
|
|
||||||
layers : [String],
|
|
||||||
view : bboxType
|
|
||||||
});
|
|
||||||
|
|
||||||
var padSchema = mongoose.Schema({
|
|
||||||
_id : String,
|
|
||||||
defaultView : { type: ObjectId, ref: "View" },
|
|
||||||
name: { type: String, default: "New FacilMap" },
|
|
||||||
writeId : String
|
|
||||||
});
|
|
||||||
|
|
||||||
var typeSchema = mongoose.Schema({
|
|
||||||
_pad : { type: String, ref: "Pad" },
|
|
||||||
name : String,
|
|
||||||
type : { type: String, enum: [ "marker", "line" ] },
|
|
||||||
fields : [ Object ]
|
|
||||||
});
|
|
||||||
|
|
||||||
var Marker = mongoose.model("Marker", markerSchema);
|
|
||||||
var Line = mongoose.model("Line", lineSchema);
|
|
||||||
var LinePoints = mongoose.model("LinePoints", linePointsSchema);
|
|
||||||
var View = mongoose.model("View", viewSchema);
|
|
||||||
var Pad = mongoose.model("Pad", padSchema);
|
|
||||||
var Type = mongoose.model("Type", typeSchema);
|
|
||||||
|
|
||||||
|
|
||||||
function migrateData(title, stream, deal, callback) {
|
|
||||||
console.log();
|
|
||||||
console.log();
|
|
||||||
console.log("Migrating "+title);
|
|
||||||
console.log();
|
|
||||||
|
|
||||||
var queue = async.queue(function(data, next) {
|
|
||||||
data = JSON.parse(JSON.stringify(data));
|
|
||||||
data.id = data._id;
|
|
||||||
delete data._id;
|
|
||||||
|
|
||||||
var queries = deal(data);
|
|
||||||
|
|
||||||
async.each(queries, function(it, next) {
|
|
||||||
it.complete(function(err) {
|
|
||||||
err && console.error(err);
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
}, next);
|
|
||||||
}, 5);
|
|
||||||
|
|
||||||
var startStop = function() {
|
|
||||||
if(queue.length() == 0)
|
|
||||||
stream.resume();
|
|
||||||
else
|
|
||||||
stream.pause();
|
|
||||||
};
|
|
||||||
|
|
||||||
stream.on("data", function(data) {
|
|
||||||
queue.push(data, startStop);
|
|
||||||
startStop();
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on("end", function() {
|
|
||||||
if(queue.running() == 0 && queue.length() == 0)
|
|
||||||
callback();
|
|
||||||
else
|
|
||||||
queue.drain = callback;
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on("error", function(err) {
|
|
||||||
queue.kill();
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
var DEFAULT_MARKER_TYPE = db2._defaultTypes[0];
|
|
||||||
var DEFAULT_LINE_TYPE = db2._defaultTypes[1];
|
|
||||||
|
|
||||||
|
|
||||||
var rl = readline.createInterface({
|
|
||||||
input: process.stdin,
|
|
||||||
output: process.stdout
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log("Target SQL database will be cleared!");
|
|
||||||
rl.question("Please enter the MongoDB connection string [mongodb://localhost/facilpad]: ", function(connectionString) {
|
|
||||||
rl.close();
|
|
||||||
|
|
||||||
mongoose.connect(connectionString || "mongodb://localhost/facilpad");
|
|
||||||
|
|
||||||
var markerTypes = { };
|
|
||||||
var lineTypes = { };
|
|
||||||
var markers = { };
|
|
||||||
var lines = { };
|
|
||||||
var views = { };
|
|
||||||
|
|
||||||
async.series([
|
|
||||||
function(next) {
|
|
||||||
db.connect(next, true);
|
|
||||||
},
|
|
||||||
function(next) {
|
|
||||||
migrateData("Pads", Pad.find().stream(), function(data) {
|
|
||||||
delete data.defaultView;
|
|
||||||
|
|
||||||
var ret = [ db._models.Pad.create(data) ];
|
|
||||||
|
|
||||||
markerTypes[data.id] = db._models.Type.build(DEFAULT_MARKER_TYPE);
|
|
||||||
markerTypes[data.id].padId = data.id;
|
|
||||||
ret.push(markerTypes[data.id].save());
|
|
||||||
|
|
||||||
lineTypes[data.id] = db._models.Type.build(DEFAULT_LINE_TYPE);
|
|
||||||
lineTypes[data.id].padId = data.id;
|
|
||||||
ret.push(lineTypes[data.id].save());
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
function(next) {
|
|
||||||
migrateData("Markers", Marker.find().stream(), function(data) {
|
|
||||||
data.lat = (data.position && data.position.lat);
|
|
||||||
data.lon = (data.position && data.position.lon);
|
|
||||||
delete data.position;
|
|
||||||
|
|
||||||
data.padId = data._pad;
|
|
||||||
delete data._pad;
|
|
||||||
|
|
||||||
if(data.style) {
|
|
||||||
data.colour = OLD_MARKER_COLOURS[data.style];
|
|
||||||
delete data.style;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.typeId = (markerTypes[data.padId] && markerTypes[data.padId].id);
|
|
||||||
|
|
||||||
var id = data.id;
|
|
||||||
delete data.id;
|
|
||||||
|
|
||||||
markers[id] = db._models.Marker.build(data);
|
|
||||||
|
|
||||||
return [ markers[id].save() ];
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
function(next) {
|
|
||||||
migrateData("MarkerData", Marker.find().stream(), function(data) {
|
|
||||||
if(!markers[data.id].id)
|
|
||||||
return [ ]; // Saving of marker failed
|
|
||||||
|
|
||||||
var markerData = db._models.MarkerData.build({ name: "Description", value: data.description || "" });
|
|
||||||
markerData.markerId = markers[data.id].id;
|
|
||||||
return [ markerData.save() ];
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
function(next) {
|
|
||||||
migrateData("Line", Line.find().stream(), function(data) {
|
|
||||||
data.padId = data._pad;
|
|
||||||
delete data._pad;
|
|
||||||
|
|
||||||
for(var i=0; i<data.points.length; i++) {
|
|
||||||
delete data.points[i]._id;
|
|
||||||
}
|
|
||||||
|
|
||||||
data.typeId = (lineTypes[data.padId] && lineTypes[data.padId].id);
|
|
||||||
|
|
||||||
var id = data.id;
|
|
||||||
delete data.id;
|
|
||||||
|
|
||||||
lines[id] = db._models.Line.build(data);
|
|
||||||
|
|
||||||
return [ lines[id].save() ];
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
function(next) {
|
|
||||||
migrateData("LineData", Line.find().stream(), function(data) {
|
|
||||||
if(!lines[data.id].id)
|
|
||||||
return [ ]; // Saving of line failed
|
|
||||||
|
|
||||||
var lineData = db._models.LineData.build({ name: "Description", value: data.description || "" });
|
|
||||||
lineData.lineId = lines[data.id].id;
|
|
||||||
return [ lineData.save() ];
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
function(next) {
|
|
||||||
migrateData("LinePoints", LinePoints.find().stream(), function(data) {
|
|
||||||
delete data.id;
|
|
||||||
|
|
||||||
var linePoint = db._models.LinePoint.build(data);
|
|
||||||
linePoint.lineId = lines[data._line].id;
|
|
||||||
return [ linePoint.save() ];
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
function(next) {
|
|
||||||
migrateData("Views", View.find().stream(), function(data) {
|
|
||||||
var id = data.id;
|
|
||||||
delete data.id;
|
|
||||||
|
|
||||||
data.top = data.view.top;
|
|
||||||
data.left = data.view.left;
|
|
||||||
data.bottom = data.view.bottom;
|
|
||||||
data.right = data.view.right;
|
|
||||||
delete data.view;
|
|
||||||
|
|
||||||
data.padId = data._pad;
|
|
||||||
delete data._pad;
|
|
||||||
|
|
||||||
views[id] = db._models.View.build(data);
|
|
||||||
return [ views[id].save() ];
|
|
||||||
}, next);
|
|
||||||
},
|
|
||||||
function(next) {
|
|
||||||
migrateData("defaultViews", Pad.find().stream(), function(data) {
|
|
||||||
if(!data.defaultView)
|
|
||||||
return [ ];
|
|
||||||
|
|
||||||
return [ db._models.Pad.update({ defaultViewId: views[data.defaultView] && views[data.defaultView].id }, { where: { id: data.id } }) ];
|
|
||||||
}, next);
|
|
||||||
}
|
|
||||||
], function(err) {
|
|
||||||
if(err)
|
|
||||||
console.error(err);
|
|
||||||
|
|
||||||
mongoose.disconnect();
|
|
||||||
})
|
|
||||||
});
|
|
|
@ -1,6 +1,4 @@
|
||||||
var database = require("./database");
|
|
||||||
var utils = require("./utils");
|
var utils = require("./utils");
|
||||||
var Promise = require("promise");
|
|
||||||
|
|
||||||
var _e = utils.escapeXml;
|
var _e = utils.escapeXml;
|
||||||
|
|
||||||
|
@ -45,28 +43,36 @@ function _textToData(fields, text) {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function exportGpx(padId, useTracks) {
|
function exportGpx(database, padId, useTracks) {
|
||||||
var padDataP = database.getPadData(padId);
|
return utils.promiseAuto({
|
||||||
|
padData: database.getPadData(padId),
|
||||||
|
|
||||||
var views = '', markers = '', lines = '', types = '';
|
views: () => {
|
||||||
|
var views = '';
|
||||||
|
return utils.streamEachPromise(database.getViews(padId), (view) => {
|
||||||
|
views += '<fm:view name="' + _e(view.name) + '" baselayer="' + _e(view.baseLayer) + '" layers="' + _e(JSON.stringify(view.layers)) + '" bbox="' + _e([ view.left, view.top, view.right, view.bottom].join(',')) + '" />\n';
|
||||||
|
}).then(() => views);
|
||||||
|
},
|
||||||
|
|
||||||
var viewsP = utils.streamEachPromise(database.getViews(padId), function(view) {
|
typesObj: () => {
|
||||||
views += '<fm:view name="' + _e(view.name) + '" baselayer="' + _e(view.baseLayer) + '" layers="' + _e(JSON.stringify(view.layers)) + '" bbox="' + _e([ view.left, view.top, view.right, view.bottom].join(',')) + '" />\n';
|
var typesObj = { };
|
||||||
});
|
return utils.streamEachPromise(database.getTypes(padId), function(type) {
|
||||||
|
typesObj[type.id] = type;
|
||||||
|
}).then(() => typesObj);
|
||||||
|
},
|
||||||
|
|
||||||
var typesObj = { };
|
types: (typesObj) => {
|
||||||
var typesObjP = utils.streamEachPromise(database.getTypes(padId), function(type) {
|
var types = '';
|
||||||
typesObj[type.id] = type;
|
for(var i in typesObj) {
|
||||||
});
|
var type = typesObj[i];
|
||||||
|
types += '<fm:type name="' + _e(type.name) + '" type="' + _e(type.type) + '" fields="' + _e(JSON.stringify(type.fields)) + '" />\n';
|
||||||
|
}
|
||||||
|
return types;
|
||||||
|
},
|
||||||
|
|
||||||
var typesMarkersLinesP = typesObjP.then(function() {
|
markers: (typesObj) => {
|
||||||
for(var i in typesObj) {
|
var markers = '';
|
||||||
var type = typesObj[i];
|
return utils.streamEachPromise(database.getPadMarkers(padId), function(marker) {
|
||||||
types += '<fm:type name="' + _e(type.name) + '" type="' + _e(type.type) + '" fields="' + _e(JSON.stringify(type.fields)) + '" />\n';
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all([
|
|
||||||
utils.streamEachPromise(database.getPadMarkers(padId), function(marker) {
|
|
||||||
markers += '<wpt lat="' + _e(marker.lat) + '" lon="' + _e(marker.lon) + '">\n' +
|
markers += '<wpt lat="' + _e(marker.lat) + '" lon="' + _e(marker.lon) + '">\n' +
|
||||||
'\t<name>' + _e(marker.name) + '</name>\n' +
|
'\t<name>' + _e(marker.name) + '</name>\n' +
|
||||||
'\t<desc>' + _e(_dataToText(typesObj[marker.typeId].fields, marker.data)) + '</desc>\n' +
|
'\t<desc>' + _e(_dataToText(typesObj[marker.typeId].fields, marker.data)) + '</desc>\n' +
|
||||||
|
@ -74,8 +80,12 @@ function exportGpx(padId, useTracks) {
|
||||||
'\t\t<fm:colour>' + _e(marker.colour) + '</fm:colour>\n' +
|
'\t\t<fm:colour>' + _e(marker.colour) + '</fm:colour>\n' +
|
||||||
'\t</extensions>\n' +
|
'\t</extensions>\n' +
|
||||||
'</wpt>\n';
|
'</wpt>\n';
|
||||||
}),
|
}).then(() => markers);
|
||||||
utils.streamEachPromise(database.getPadLinesWithPoints(padId), function(line) {
|
},
|
||||||
|
|
||||||
|
lines: (typesObj) => {
|
||||||
|
var lines = '';
|
||||||
|
return utils.streamEachPromise(database.getPadLinesWithPoints(padId), function(line) {
|
||||||
var t = (useTracks || line.mode == "track");
|
var t = (useTracks || line.mode == "track");
|
||||||
|
|
||||||
lines += '<' + (t ? 'trk' : 'rte') + '>\n' +
|
lines += '<' + (t ? 'trk' : 'rte') + '>\n' +
|
||||||
|
@ -101,25 +111,22 @@ function exportGpx(padId, useTracks) {
|
||||||
}
|
}
|
||||||
|
|
||||||
lines += '</' + (t ? 'trk' : 'rte') + '>\n';
|
lines += '</' + (t ? 'trk' : 'rte') + '>\n';
|
||||||
})
|
}).then(() => lines);
|
||||||
]);
|
}
|
||||||
});
|
}).then((res) => '<?xml version="1.0" encoding="UTF-8"?>\n' +
|
||||||
|
|
||||||
return Promise.all([ padDataP, viewsP, typesMarkersLinesP ]).then(function(res) {
|
|
||||||
return '<?xml version="1.0" encoding="UTF-8"?>\n' +
|
|
||||||
'<gpx xmlns="http://www.topografix.com/GPX/1/1" creator="FacilMap" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" xmlns:fm="https://facilmap.org/">\n' +
|
'<gpx xmlns="http://www.topografix.com/GPX/1/1" creator="FacilMap" version="1.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" xmlns:fm="https://facilmap.org/">\n' +
|
||||||
'\t<metadata>\n' +
|
'\t<metadata>\n' +
|
||||||
'\t\t<name>' + _e(res[0].name) + '</name>\n' +
|
'\t\t<name>' + _e(res.padData.name) + '</name>\n' +
|
||||||
'\t\t<time>' + _e(utils.isoDate()) + '</time>\n' +
|
'\t\t<time>' + _e(utils.isoDate()) + '</time>\n' +
|
||||||
'\t</metadata>\n' +
|
'\t</metadata>\n' +
|
||||||
'\t<extensions>\n' +
|
'\t<extensions>\n' +
|
||||||
views.replace(/^(.)/gm, '\t\t$1') +
|
res.views.replace(/^(.)/gm, '\t\t$1') +
|
||||||
types.replace(/^(.)/gm, '\t\t$1') +
|
res.types.replace(/^(.)/gm, '\t\t$1') +
|
||||||
'\t</extensions>\n' +
|
'\t</extensions>\n' +
|
||||||
markers.replace(/^(.)/gm, '\t$1') +
|
res.markers.replace(/^(.)/gm, '\t$1') +
|
||||||
lines.replace(/^(.)/gm, '\t$1') +
|
res.lines.replace(/^(.)/gm, '\t$1') +
|
||||||
'</gpx>';
|
'</gpx>'
|
||||||
});
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
var request = require("request-promise");
|
var request = require("request-promise");
|
||||||
|
|
||||||
var utils = require("./utils");
|
var utils = require("./utils");
|
||||||
var config = require("../config");
|
var config = require("../config");
|
||||||
var Promise = require("promise");
|
|
||||||
|
|
||||||
var ROUTING_URL = "https://api.mapbox.com/directions/v5/mapbox";
|
var ROUTING_URL = "https://api.mapbox.com/directions/v5/mapbox";
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
var request = require("request-promise");
|
var request = require("request-promise");
|
||||||
var config = require("../config");
|
|
||||||
var Promise = require("promise");
|
var Promise = require("promise");
|
||||||
var cheerio = require("cheerio");
|
var cheerio = require("cheerio");
|
||||||
var zlib = require("zlib");
|
var zlib = require("zlib");
|
||||||
var compressjs = require("compressjs");
|
var compressjs = require("compressjs");
|
||||||
|
|
||||||
|
var config = require("../config");
|
||||||
var utils = require("./utils");
|
var utils = require("./utils");
|
||||||
|
|
||||||
request = request.defaults({
|
request = request.defaults({
|
||||||
|
|
392
server/server.js
392
server/server.js
|
@ -1,17 +1,14 @@
|
||||||
var http = require("http");
|
var http = require("http");
|
||||||
var socketIo = require("socket.io");
|
var compression = require("compression");
|
||||||
var config = require("../config");
|
|
||||||
var listeners = require("./listeners");
|
|
||||||
var database = require("./database");
|
|
||||||
var domain = require("domain");
|
var domain = require("domain");
|
||||||
var utils = require("./utils");
|
|
||||||
var routing = require("./routing");
|
|
||||||
var gpx = require("./gpx");
|
|
||||||
var search = require("./search");
|
|
||||||
var Promise = require("promise");
|
var Promise = require("promise");
|
||||||
var express = require("express");
|
var express = require("express");
|
||||||
var path = require("path");
|
var path = require("path");
|
||||||
var compression = require("compression");
|
|
||||||
|
var config = require("../config");
|
||||||
|
var Database = require("./database/database");
|
||||||
|
var utils = require("./utils");
|
||||||
|
var Socket = require("./socket");
|
||||||
|
|
||||||
var frontendPath = path.resolve(__dirname + "/../frontend");
|
var frontendPath = path.resolve(__dirname + "/../frontend");
|
||||||
|
|
||||||
|
@ -28,370 +25,33 @@ Object.defineProperty(Error.prototype, "toJSON", {
|
||||||
configurable: true
|
configurable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
var dbP = database.connect();
|
process.on('unhandledRejection', (reason, promise) => {
|
||||||
|
console.trace("Unhandled rejection", reason);
|
||||||
var app = express();
|
|
||||||
|
|
||||||
app.use(compression());
|
|
||||||
|
|
||||||
app.use(express.static(frontendPath + "/build/"));
|
|
||||||
|
|
||||||
app.get("/:padId", function(req, res) {
|
|
||||||
res.sendFile(frontendPath + "/build/index.html");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var server = http.createServer(app);
|
utils.promiseAuto({
|
||||||
|
database: () => new Database(),
|
||||||
|
|
||||||
var serverP = Promise.denodeify(server.listen.bind(server))(config.port, config.host).then(function() {
|
databaseConnect: database => database.connect(),
|
||||||
var io = socketIo.listen(server);
|
|
||||||
|
|
||||||
io.sockets.on("connection", function(socket) {
|
server: () => {
|
||||||
var d = domain.create();
|
var app = express();
|
||||||
d.add(socket);
|
app.use(compression());
|
||||||
|
app.use(express.static(frontendPath + "/build/"));
|
||||||
d.on("error", function(err) {
|
app.get("/:padId", function(req, res) {
|
||||||
console.error("Uncaught error in socket:", err.stack);
|
res.sendFile(frontendPath + "/build/index.html");
|
||||||
socket.disconnect();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
var handlers = {
|
var server = http.createServer(app);
|
||||||
error : function(err) {
|
return Promise.denodeify(server.listen.bind(server))(config.port, config.host).then(() => server);
|
||||||
console.error("Error! Disconnecting client.");
|
},
|
||||||
console.error(err.stack);
|
|
||||||
socket.disconnect();
|
|
||||||
},
|
|
||||||
|
|
||||||
setPadId : function(padId) {
|
socket: (server, database) => {
|
||||||
return Promise.resolve().then(function() {
|
return new Socket(server, database);
|
||||||
if(typeof padId != "string")
|
}
|
||||||
throw "Invalid pad id";
|
}).then(res => {
|
||||||
if(socket.padId != null)
|
|
||||||
throw "Pad id already set";
|
|
||||||
|
|
||||||
socket.padId = true;
|
|
||||||
|
|
||||||
return database.getPadData(padId);
|
|
||||||
}).then(function(data) {
|
|
||||||
return _setPadId(socket, data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
updateBbox : function(bbox) {
|
|
||||||
if(!utils.stripObject(bbox, { top: "number", left: "number", bottom: "number", right: "number", zoom: "number" }))
|
|
||||||
return;
|
|
||||||
|
|
||||||
var bboxWithExcept = utils.extend({ }, bbox);
|
|
||||||
if(socket.bbox && bbox.zoom == socket.bbox.zoom)
|
|
||||||
bboxWithExcept.except = socket.bbox;
|
|
||||||
|
|
||||||
socket.bbox = bbox;
|
|
||||||
|
|
||||||
if(socket.padId && socket.padId !== true) {
|
|
||||||
return utils.promiseAllObject({
|
|
||||||
marker: utils.streamToArrayPromise(database.getPadMarkers(socket.padId, bboxWithExcept)),
|
|
||||||
linePoints: utils.streamToArrayPromise(database.getLinePoints(socket.padId, bboxWithExcept))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
disconnect : function() {
|
|
||||||
if(socket.padId)
|
|
||||||
listeners.removePadListener(socket);
|
|
||||||
},
|
|
||||||
|
|
||||||
createPad : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { name: "string", defaultViewId: "number", id: "string", writeId: "string" }))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
if(socket.padId)
|
|
||||||
throw "Pad already loaded.";
|
|
||||||
|
|
||||||
return database.createPad(data);
|
|
||||||
}).then(function(padData) {
|
|
||||||
return _setPadId(socket, padData);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
editPad : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { name: "string", defaultViewId: "number", id: "string", writeId: "string" }))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
if(!socket.writable)
|
|
||||||
throw "In read-only mode.";
|
|
||||||
|
|
||||||
return database.updatePadData(socket.padId, data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
addMarker : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { lat: "number", lon: "number", name: "string", colour: "string", size: "number", symbol: "string", typeId: "number", data: Object } ))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
if(!socket.writable)
|
|
||||||
throw "In read-only mode.";
|
|
||||||
|
|
||||||
return database.createMarker(socket.padId, data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
editMarker : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { id: "number", lat: "number", lon: "number", name: "string", colour: "string", size: "number", symbol: "string", typeId: "number", data: Object }))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
if(!socket.writable)
|
|
||||||
throw "In read-only mode.";
|
|
||||||
|
|
||||||
return database.updateMarker(data.id, data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteMarker : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { id: "number" }))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
if(!socket.writable)
|
|
||||||
throw "In read-only mode.";
|
|
||||||
|
|
||||||
return database.deleteMarker(data.id);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getLineTemplate : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { typeId: "number" }) || data.typeId == null)
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
return database.getLineTemplate(data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
addLine : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { routePoints: [ { lat: "number", lon: "number" } ], trackPoints: [ { lat: "number", lon: "number" } ], mode: "string", colour: "string", width: "number", name: "string", typeId: "number", data: Object }))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
if(!socket.writable)
|
|
||||||
throw "In read-only mode.";
|
|
||||||
|
|
||||||
return database.createLine(socket.padId, data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
editLine : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { id: "number", routePoints: [ { lat: "number", lon: "number" } ], trackPoints: [ { lat: "number", lon: "number" } ], mode: "string", colour: "string", width: "number", name: "string", typeId: "number", data: Object }))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
if(!socket.writable)
|
|
||||||
throw "In read-only mode.";
|
|
||||||
|
|
||||||
return database.updateLine(data.id, data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteLine : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { id: "number" }))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
if(!socket.writable)
|
|
||||||
throw "In read-only mode.";
|
|
||||||
|
|
||||||
return database.deleteLine(data.id);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
addView : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { name: "string", baseLayer: "string", layers: [ "string" ], top: "number", left: "number", right: "number", bottom: "number", filter: "string" }))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
if(!socket.writable)
|
|
||||||
throw "In read-only mode.";
|
|
||||||
|
|
||||||
return database.createView(socket.padId, data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
editView : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { id: "number", baseLayer: "string", layers: [ "string" ], top: "number", left: "number", right: "number", bottom: "number", filter: "string" }))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
if(!socket.writable)
|
|
||||||
throw "In read-only mode.";
|
|
||||||
|
|
||||||
return database.updateView(data.id, data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteView : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { id: "number" }))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
if(!socket.writable)
|
|
||||||
throw "In read-only mode.";
|
|
||||||
|
|
||||||
return database.deleteView(data.id);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
addType : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, {
|
|
||||||
id: "number",
|
|
||||||
name: "string",
|
|
||||||
type: "string",
|
|
||||||
defaultColour: "string", colourFixed: "boolean",
|
|
||||||
defaultSize: "number", sizeFixed: "boolean",
|
|
||||||
defaultSymbol: "string", symbolFixed: "boolean",
|
|
||||||
defaultWidth: "number", widthFixed: "boolean",
|
|
||||||
defaultMode: "string", modeFixed: "boolean",
|
|
||||||
fields: [ {
|
|
||||||
name: "string",
|
|
||||||
type: "string",
|
|
||||||
default: "string",
|
|
||||||
controlColour: "boolean", controlSize: "boolean", controlSymbol: "boolean", controlWidth: "boolean",
|
|
||||||
options: [ { key: "string", value: "string", colour: "string", size: "number", "symbol": "string", width: "number" } ]
|
|
||||||
}]
|
|
||||||
}))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
if(!socket.writable)
|
|
||||||
throw "In read-only mode.";
|
|
||||||
|
|
||||||
return database.createType(socket.padId, data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
editType : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, {
|
|
||||||
id: "number",
|
|
||||||
name: "string",
|
|
||||||
defaultColour: "string", colourFixed: "boolean",
|
|
||||||
defaultSize: "number", sizeFixed: "boolean",
|
|
||||||
defaultSymbol: "string", symbolFixed: "boolean",
|
|
||||||
defaultWidth: "number", widthFixed: "boolean",
|
|
||||||
defaultMode: "string", modeFixed: "boolean",
|
|
||||||
fields: [ {
|
|
||||||
name: "string",
|
|
||||||
type: "string",
|
|
||||||
default: "string",
|
|
||||||
controlColour: "boolean", controlSize: "boolean", controlSymbol: "boolean", controlWidth: "boolean",
|
|
||||||
options: [ { key: "string", value: "string", colour: "string", size: "number", "symbol": "string", width: "number" } ]
|
|
||||||
}]
|
|
||||||
}))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
if(!socket.writable)
|
|
||||||
throw "In read-only mode.";
|
|
||||||
|
|
||||||
return database.updateType(data.id, data);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteType : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { id: "number" }))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
if(!socket.writable)
|
|
||||||
throw "In read-only mode.";
|
|
||||||
|
|
||||||
return database.deleteType(data.id);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
exportGpx : function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(socket.padId == null)
|
|
||||||
throw "No pad ID set.";
|
|
||||||
|
|
||||||
return gpx.exportGpx(socket.padId, data.useTracks);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
find: function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { query: "string", loadUrls: "boolean" }))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
return search.find(data.query, data.loadUrls);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getRoute: function(data) {
|
|
||||||
return Promise.resolve().then(function() {
|
|
||||||
if(!utils.stripObject(data, { destinations: [ { lat: "number", lon: "number" } ], mode: "string" }))
|
|
||||||
throw "Invalid parameters.";
|
|
||||||
|
|
||||||
return routing.calculateRouting(data.destinations, data.mode, true);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/*copyPad : function(data, callback) {
|
|
||||||
if(!utils.stripObject(data, { toId: "string" }))
|
|
||||||
return callback("Invalid parameters.");
|
|
||||||
|
|
||||||
database.copyPad(socket.padId, data.toId, callback);
|
|
||||||
}*/
|
|
||||||
};
|
|
||||||
|
|
||||||
for(var i in handlers) { (function(i) {
|
|
||||||
socket.on(i, function(data, callback) {
|
|
||||||
Promise.resolve(data).then(handlers[i]).then(function(res) { // nodeify(callback);
|
|
||||||
callback(null, res);
|
|
||||||
}, function(err) {
|
|
||||||
console.log(err.stack);
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})(i); }
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
Promise.all([ dbP, serverP ]).then(function() {
|
|
||||||
console.log("Server started on " + (config.host || "*" ) + ":" + config.port);
|
console.log("Server started on " + (config.host || "*" ) + ":" + config.port);
|
||||||
}).catch(function(err) {
|
}).catch(err => {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
function _sendStreamData(socket, eventName, stream) {
|
|
||||||
stream.on("data", function(data) {
|
|
||||||
if(data != null)
|
|
||||||
socket.emit(eventName, data);
|
|
||||||
}).on("error", function(err) {
|
|
||||||
console.warn("_sendStreamData", err, err.stack);
|
|
||||||
socket.emit("error", err);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function _setPadId(socket, data) {
|
|
||||||
socket.padId = data.id;
|
|
||||||
socket.writable = data.writable;
|
|
||||||
listeners.addPadListener(socket);
|
|
||||||
|
|
||||||
var promises = {
|
|
||||||
padData: [ data ],
|
|
||||||
view: utils.streamToArrayPromise(database.getViews(socket.padId)),
|
|
||||||
type: utils.streamToArrayPromise(database.getTypes(socket.padId)),
|
|
||||||
line: utils.streamToArrayPromise(database.getPadLines(socket.padId))
|
|
||||||
};
|
|
||||||
|
|
||||||
if(socket.bbox) { // In case bbox is set while fetching pad data
|
|
||||||
utils.extend(promises, {
|
|
||||||
marker: utils.streamToArrayPromise(database.getPadMarkers(socket.padId, socket.bbox)),
|
|
||||||
linePoints: utils.streamToArrayPromise(database.getLinePoints(socket.padId, socket.bbox))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return utils.promiseAllObject(promises);
|
|
||||||
}
|
|
|
@ -0,0 +1,526 @@
|
||||||
|
var socketIo = require("socket.io");
|
||||||
|
var domain = require("domain");
|
||||||
|
|
||||||
|
var utils = require("./utils");
|
||||||
|
var routing = require("./routing");
|
||||||
|
var search = require("./search");
|
||||||
|
var gpx = require("./gpx");
|
||||||
|
|
||||||
|
class Socket {
|
||||||
|
constructor(server, database) {
|
||||||
|
var io = socketIo.listen(server);
|
||||||
|
|
||||||
|
io.sockets.on("connection", (socket) => {
|
||||||
|
var d = domain.create();
|
||||||
|
d.add(socket);
|
||||||
|
|
||||||
|
d.on("error", function(err) {
|
||||||
|
console.error("Uncaught error in socket:", err.stack);
|
||||||
|
socket.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
new SocketConnection(socket, database);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SocketConnection {
|
||||||
|
constructor(socket, database) {
|
||||||
|
this.socket = socket;
|
||||||
|
this.database = database;
|
||||||
|
|
||||||
|
this.padId = null;
|
||||||
|
this.bbox = null;
|
||||||
|
this.writable = null;
|
||||||
|
|
||||||
|
this._dbHandlers = [ ];
|
||||||
|
|
||||||
|
this.registerSocketHandlers();
|
||||||
|
}
|
||||||
|
|
||||||
|
registerSocketHandlers() {
|
||||||
|
Object.keys(this.socketHandlers).forEach((i) => {
|
||||||
|
this.socket.on(i, (data, callback) => {
|
||||||
|
Promise.resolve(data).then(this.socketHandlers[i].bind(this)).then((res) => { // nodeify(callback);
|
||||||
|
if(!callback && res)
|
||||||
|
console.trace("No callback available to send result of socket handler " + i);
|
||||||
|
|
||||||
|
callback && callback(null, res);
|
||||||
|
}, (err) => {
|
||||||
|
console.log(err.stack);
|
||||||
|
|
||||||
|
callback && callback(err);
|
||||||
|
}).catch(err => {
|
||||||
|
console.error("Error in socket handler for "+i, err.stack);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
registerDatabaseHandler(eventName, handler) {
|
||||||
|
var func = handler.bind(this);
|
||||||
|
|
||||||
|
this.database.on(eventName, func);
|
||||||
|
|
||||||
|
if(!this._dbHandlers[eventName])
|
||||||
|
this._dbHandlers[eventName] = [ ];
|
||||||
|
|
||||||
|
this._dbHandlers[eventName].push(func);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
this.database.removeListener(eventName, func);
|
||||||
|
this._dbHandlers[eventName] = this._dbHandlers[eventName].filter(function(it) { return it !== func; });
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
registerDatabaseHandlers() {
|
||||||
|
Object.keys(this.databaseHandlers).forEach((eventName) => {
|
||||||
|
this.registerDatabaseHandler(eventName, this.databaseHandlers[eventName]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterDatabaseHandlers() {
|
||||||
|
Object.keys(this._dbHandlers).forEach((eventName) => {
|
||||||
|
this._dbHandlers[eventName].forEach((it) => {
|
||||||
|
this.database.removeListener(eventName, it);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this._dbHandlers = { };
|
||||||
|
}
|
||||||
|
|
||||||
|
sendStreamData(eventName, stream) {
|
||||||
|
stream.on("data", (data) => {
|
||||||
|
if(data != null)
|
||||||
|
this.socket.emit(eventName, data);
|
||||||
|
}).on("error", (err) => {
|
||||||
|
console.warn("SocketConnection.sendStreamData", err.stack);
|
||||||
|
this.socket.emit("error", err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getPadObjects(padData) {
|
||||||
|
var promises = {
|
||||||
|
padData: [ padData ],
|
||||||
|
view: utils.streamToArrayPromise(this.database.getViews(padData.id)),
|
||||||
|
type: utils.streamToArrayPromise(this.database.getTypes(padData.id)),
|
||||||
|
line: utils.streamToArrayPromise(this.database.getPadLines(padData.id))
|
||||||
|
};
|
||||||
|
|
||||||
|
if(this.bbox) { // In case bbox is set while fetching pad data
|
||||||
|
utils.extend(promises, {
|
||||||
|
marker: utils.streamToArrayPromise(this.database.getPadMarkers(padData.id, this.bbox)),
|
||||||
|
linePoints: utils.streamToArrayPromise(this.database.getLinePointsForPad(padData.id, this.bbox))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return utils.promiseAllObject(promises);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.extend(SocketConnection.prototype, {
|
||||||
|
socketHandlers: {
|
||||||
|
error : function(err) {
|
||||||
|
console.error("Error! Disconnecting client.");
|
||||||
|
console.error(err.stack);
|
||||||
|
this.socket.disconnect();
|
||||||
|
},
|
||||||
|
|
||||||
|
setPadId : function(padId) {
|
||||||
|
return utils.promiseAuto({
|
||||||
|
validate: () => {
|
||||||
|
if(typeof padId != "string")
|
||||||
|
throw "Invalid pad id";
|
||||||
|
if(this.padId != null)
|
||||||
|
throw "Pad id already set";
|
||||||
|
|
||||||
|
this.padId = true;
|
||||||
|
},
|
||||||
|
|
||||||
|
write: (validate) => {
|
||||||
|
return this.database.getPadDataByWriteId(padId);
|
||||||
|
},
|
||||||
|
|
||||||
|
read: (validate) => {
|
||||||
|
return this.database.getPadData(padId);
|
||||||
|
},
|
||||||
|
|
||||||
|
pad: (write, read) => {
|
||||||
|
if(write)
|
||||||
|
return utils.extend(JSON.parse(JSON.stringify(write)), { writable: true });
|
||||||
|
else if(read)
|
||||||
|
return utils.extend(JSON.parse(JSON.stringify(read)), { writable: false, writeId: null });
|
||||||
|
else
|
||||||
|
throw "This pad does not exist";
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
this.padId = res.pad.id;
|
||||||
|
this.writable = res.pad.writable;
|
||||||
|
|
||||||
|
this.registerDatabaseHandlers();
|
||||||
|
|
||||||
|
return this.getPadObjects(res.pad);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
updateBbox : function(bbox) {
|
||||||
|
if(!utils.stripObject(bbox, { top: "number", left: "number", bottom: "number", right: "number", zoom: "number" }))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var bboxWithExcept = utils.extend({ }, bbox);
|
||||||
|
if(this.bbox && bbox.zoom == this.bbox.zoom)
|
||||||
|
bboxWithExcept.except = this.bbox;
|
||||||
|
|
||||||
|
this.bbox = bbox;
|
||||||
|
|
||||||
|
if(this.padId && this.padId !== true) {
|
||||||
|
return utils.promiseAllObject({
|
||||||
|
marker: utils.streamToArrayPromise(this.database.getPadMarkers(this.padId, bboxWithExcept)),
|
||||||
|
linePoints: utils.streamToArrayPromise(this.database.getLinePointsForPad(this.padId, bboxWithExcept))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
disconnect : function() {
|
||||||
|
if(this.padId)
|
||||||
|
this.unregisterDatabaseHandlers();
|
||||||
|
},
|
||||||
|
|
||||||
|
createPad : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { name: "string", defaultViewId: "number", id: "string", writeId: "string" }))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
if(this.padId)
|
||||||
|
throw "Pad already loaded.";
|
||||||
|
|
||||||
|
return this.database.createPad(data);
|
||||||
|
}).then((padData) => {
|
||||||
|
this.padId = padData.id;
|
||||||
|
this.writable = true;
|
||||||
|
|
||||||
|
this.registerDatabaseHandlers();
|
||||||
|
|
||||||
|
return this.getPadObjects(padData);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
editPad : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { name: "string", defaultViewId: "number", id: "string", writeId: "string" }))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
if(!this.writable)
|
||||||
|
throw "In read-only mode.";
|
||||||
|
|
||||||
|
return this.database.updatePadData(this.padId, data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addMarker : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { lat: "number", lon: "number", name: "string", colour: "string", size: "number", symbol: "string", typeId: "number", data: Object } ))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
if(!this.writable)
|
||||||
|
throw "In read-only mode.";
|
||||||
|
|
||||||
|
return this.database.createMarker(this.padId, data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
editMarker : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { id: "number", lat: "number", lon: "number", name: "string", colour: "string", size: "number", symbol: "string", typeId: "number", data: Object }))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
if(!this.writable)
|
||||||
|
throw "In read-only mode.";
|
||||||
|
|
||||||
|
return this.database.updateMarker(this.padId, data.id, data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteMarker : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { id: "number" }))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
if(!this.writable)
|
||||||
|
throw "In read-only mode.";
|
||||||
|
|
||||||
|
return this.database.deleteMarker(this.padId, data.id);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getLineTemplate : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { typeId: "number" }) || data.typeId == null)
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
return this.database.getLineTemplate(this.padId, data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addLine : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { routePoints: [ { lat: "number", lon: "number" } ], trackPoints: [ { lat: "number", lon: "number" } ], mode: "string", colour: "string", width: "number", name: "string", typeId: "number", data: Object }))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
if(!this.writable)
|
||||||
|
throw "In read-only mode.";
|
||||||
|
|
||||||
|
return this.database.createLine(this.padId, data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
editLine : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { id: "number", routePoints: [ { lat: "number", lon: "number" } ], trackPoints: [ { lat: "number", lon: "number" } ], mode: "string", colour: "string", width: "number", name: "string", typeId: "number", data: Object }))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
if(!this.writable)
|
||||||
|
throw "In read-only mode.";
|
||||||
|
|
||||||
|
return this.database.updateLine(this.padId, data.id, data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteLine : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { id: "number" }))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
if(!this.writable)
|
||||||
|
throw "In read-only mode.";
|
||||||
|
|
||||||
|
return this.database.deleteLine(this.padId, data.id);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addView : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { name: "string", baseLayer: "string", layers: [ "string" ], top: "number", left: "number", right: "number", bottom: "number", filter: "string" }))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
if(!this.writable)
|
||||||
|
throw "In read-only mode.";
|
||||||
|
|
||||||
|
return this.database.createView(this.padId, data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
editView : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { id: "number", baseLayer: "string", layers: [ "string" ], top: "number", left: "number", right: "number", bottom: "number", filter: "string" }))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
if(!this.writable)
|
||||||
|
throw "In read-only mode.";
|
||||||
|
|
||||||
|
return this.database.updateView(this.padId, data.id, data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteView : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { id: "number" }))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
if(!this.writable)
|
||||||
|
throw "In read-only mode.";
|
||||||
|
|
||||||
|
return this.database.deleteView(this.padId, data.id);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
addType : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, {
|
||||||
|
id: "number",
|
||||||
|
name: "string",
|
||||||
|
type: "string",
|
||||||
|
defaultColour: "string", colourFixed: "boolean",
|
||||||
|
defaultSize: "number", sizeFixed: "boolean",
|
||||||
|
defaultSymbol: "string", symbolFixed: "boolean",
|
||||||
|
defaultWidth: "number", widthFixed: "boolean",
|
||||||
|
defaultMode: "string", modeFixed: "boolean",
|
||||||
|
fields: [ {
|
||||||
|
name: "string",
|
||||||
|
type: "string",
|
||||||
|
default: "string",
|
||||||
|
controlColour: "boolean", controlSize: "boolean", controlSymbol: "boolean", controlWidth: "boolean",
|
||||||
|
options: [ { key: "string", value: "string", colour: "string", size: "number", "symbol": "string", width: "number" } ]
|
||||||
|
}]
|
||||||
|
}))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
if(!this.writable)
|
||||||
|
throw "In read-only mode.";
|
||||||
|
|
||||||
|
return this.database.createType(this.padId, data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
editType : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, {
|
||||||
|
id: "number",
|
||||||
|
name: "string",
|
||||||
|
defaultColour: "string", colourFixed: "boolean",
|
||||||
|
defaultSize: "number", sizeFixed: "boolean",
|
||||||
|
defaultSymbol: "string", symbolFixed: "boolean",
|
||||||
|
defaultWidth: "number", widthFixed: "boolean",
|
||||||
|
defaultMode: "string", modeFixed: "boolean",
|
||||||
|
fields: [ {
|
||||||
|
name: "string",
|
||||||
|
type: "string",
|
||||||
|
default: "string",
|
||||||
|
controlColour: "boolean", controlSize: "boolean", controlSymbol: "boolean", controlWidth: "boolean",
|
||||||
|
options: [ { key: "string", value: "string", colour: "string", size: "number", "symbol": "string", width: "number" } ]
|
||||||
|
}]
|
||||||
|
}))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
if(!this.writable)
|
||||||
|
throw "In read-only mode.";
|
||||||
|
|
||||||
|
return this.database.updateType(this.padId, data.id, data);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteType : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { id: "number" }))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
if(!this.writable)
|
||||||
|
throw "In read-only mode.";
|
||||||
|
|
||||||
|
return this.database.deleteType(this.padId, data.id);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
exportGpx : function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(this.padId == null)
|
||||||
|
throw "No pad ID set.";
|
||||||
|
|
||||||
|
return gpx.exportGpx(this.database, this.padId, data.useTracks);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
find: function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { query: "string", loadUrls: "boolean" }))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
return search.find(data.query, data.loadUrls);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getRoute: function(data) {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!utils.stripObject(data, { destinations: [ { lat: "number", lon: "number" } ], mode: "string" }))
|
||||||
|
throw "Invalid parameters.";
|
||||||
|
|
||||||
|
return routing.calculateRouting(data.destinations, data.mode, true);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/*listenToHistory: function() {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(this.padId == null)
|
||||||
|
throw "No pad ID set.";
|
||||||
|
|
||||||
|
if(this.historyListener)
|
||||||
|
throw "Already listening to history.";
|
||||||
|
|
||||||
|
this.historyListener = this.registerDatabaseHandler("addHistoryEntry", function(data) {
|
||||||
|
this.socket.emit("history", data);
|
||||||
|
});
|
||||||
|
|
||||||
|
return utils.promiseAllObject({
|
||||||
|
history: utils.streamToArrayPromise(this.database.getHistory(this.padId))
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
stopListeningToHistory: function() {
|
||||||
|
return Promise.resolve().then(() => {
|
||||||
|
if(!this.historyListener)
|
||||||
|
throw "Not listening to history.";
|
||||||
|
|
||||||
|
this.historyListener(); // Unregister db listener
|
||||||
|
this.historyListener = null;
|
||||||
|
});
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/*copyPad : function(data, callback) {
|
||||||
|
if(!utils.stripObject(data, { toId: "string" }))
|
||||||
|
return callback("Invalid parameters.");
|
||||||
|
|
||||||
|
this.database.copyPad(this.padId, data.toId, callback);
|
||||||
|
}*/
|
||||||
|
},
|
||||||
|
|
||||||
|
databaseHandlers: {
|
||||||
|
line: function(padId, data) {
|
||||||
|
if(padId == this.padId)
|
||||||
|
this.socket.emit("line", data);
|
||||||
|
},
|
||||||
|
|
||||||
|
linePoints: function(padId, lineId, trackPoints) {
|
||||||
|
if(padId == this.padId)
|
||||||
|
this.socket.emit("linePoints", { reset: true, id: lineId, trackPoints : (this.bbox ? routing.prepareForBoundingBox(trackPoints, this.bbox) : [ ]) });
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteLine: function(padId, data) {
|
||||||
|
if(padId == this.padId)
|
||||||
|
this.socket.emit("deleteLine", data);
|
||||||
|
},
|
||||||
|
|
||||||
|
marker: function(padId, data) {
|
||||||
|
if(padId == this.padId && this.bbox && utils.isInBbox(data, this.bbox))
|
||||||
|
this.socket.emit("marker", data);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteMarker: function(padId, data) {
|
||||||
|
if(padId == this.padId)
|
||||||
|
this.socket.emit("deleteMarker", data);
|
||||||
|
},
|
||||||
|
|
||||||
|
type: function(padId, data) {
|
||||||
|
if(padId == this.padId)
|
||||||
|
this.socket.emit("type", data);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteType: function(padId, data) {
|
||||||
|
if(padId == this.padId)
|
||||||
|
this.socket.emit("deleteType", data);
|
||||||
|
},
|
||||||
|
|
||||||
|
padData: function(padId, data) {
|
||||||
|
if(padId == this.padId) {
|
||||||
|
var dataClone = JSON.parse(JSON.stringify(data));
|
||||||
|
if(!this.writable)
|
||||||
|
dataClone.writeId = null;
|
||||||
|
|
||||||
|
this.padId = data.id;
|
||||||
|
|
||||||
|
this.socket.emit("padData", dataClone);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
view: function(padId, data) {
|
||||||
|
if(padId == this.padId)
|
||||||
|
this.socket.emit("view", data);
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteView: function(padId, data) {
|
||||||
|
if(padId == this.padId)
|
||||||
|
this.socket.emit("deleteView", data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = Socket;
|
|
@ -235,6 +235,59 @@ function promiseAllObject(obj) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function promiseAuto(obj) {
|
||||||
|
var promises = { };
|
||||||
|
|
||||||
|
function _get(str) {
|
||||||
|
if(!obj[str])
|
||||||
|
throw new Error("Invalid dependency '" + str + "' in promiseAuto().");
|
||||||
|
|
||||||
|
if(promises[str])
|
||||||
|
return promises[str];
|
||||||
|
|
||||||
|
if(obj[str].then)
|
||||||
|
return obj[str];
|
||||||
|
|
||||||
|
var params = getFuncParams(obj[str]);
|
||||||
|
return promises[str] = _getDeps(params).then(function(res) {
|
||||||
|
return obj[str].apply(null, params.map(function(param) { return res[param]; }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function _getDeps(arr) {
|
||||||
|
var deps = { };
|
||||||
|
arr.forEach(function(it) {
|
||||||
|
deps[it] = _get(it);
|
||||||
|
});
|
||||||
|
return promiseAllObject(deps);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _getDeps(Object.keys(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFuncParams(func) {
|
||||||
|
// Taken from angular injector code
|
||||||
|
|
||||||
|
var ARROW_ARG = /^([^\(]+?)\s*=>/;
|
||||||
|
var FN_ARGS = /^[^\(]*\(\s*([^\)]*)\)/m;
|
||||||
|
var FN_ARG_SPLIT = /\s*,\s*/;
|
||||||
|
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
|
||||||
|
|
||||||
|
var fnText = (Function.prototype.toString.call(func) + ' ').replace(STRIP_COMMENTS, '');
|
||||||
|
var params = (fnText.match(ARROW_ARG) || fnText.match(FN_ARGS))[1];
|
||||||
|
return params == "" ? [ ] : params.split(FN_ARG_SPLIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
function modifyFunction(obj, prop, before, after) {
|
||||||
|
var bkp = obj[prop];
|
||||||
|
obj[prop] = function() {
|
||||||
|
before && before.apply(this, arguments);
|
||||||
|
var ret = bkp.apply(this, arguments);
|
||||||
|
after && after.apply(this, arguments);
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
isInBbox : isInBbox,
|
isInBbox : isInBbox,
|
||||||
filterStreamPromise : filterStreamPromise,
|
filterStreamPromise : filterStreamPromise,
|
||||||
|
@ -248,5 +301,7 @@ module.exports = {
|
||||||
isoDate : isoDate,
|
isoDate : isoDate,
|
||||||
round: round,
|
round: round,
|
||||||
streamToArrayPromise: streamToArrayPromise,
|
streamToArrayPromise: streamToArrayPromise,
|
||||||
promiseAllObject: promiseAllObject
|
promiseAllObject: promiseAllObject,
|
||||||
|
promiseAuto: promiseAuto,
|
||||||
|
modifyFunction: modifyFunction
|
||||||
};
|
};
|
Ładowanie…
Reference in New Issue