var Sequelize = require("sequelize"); var config = require("../config"); var utils = require("./utils"); var async = require("async"); 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, unique: "padId", primaryKey: true }, name: { type: Sequelize.TEXT, allowNull: true, get: function() { return this.getDataValue("name") || "New FacilPad"; } }, writeId: { type: Sequelize.STRING, allowNull: false, unique: "padId" } }); /* 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 } }); 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= 1)) throw new Error("Invalid width "+obj[i].options[j].width+" in field "+obj[i].name+"."); } } } } } } }); 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(callback, force) { async.series([ function(next) { _promiseComplete(conn.authenticate(), next); }, function(next) { _promiseComplete(conn.sync({ force: !!force }), next); }, function(next) { // Migrations var queryInterface = conn.getQueryInterface(); // Rename Line.points to Line.routePoints queryInterface.describeTable('Lines').then(function(attributes) { if(attributes.points) { return queryInterface.renameColumn('Lines', 'points', 'routePoints'); } }) .then(function() { next(); }) .catch(function(err) { next(err); }); } ], callback); } function getPadData(padId, callback) { _promiseComplete(Pad.findOne({ where: { id: padId }, include: [ { model: View, as: "defaultView" } ]}), callback); } function getPadDataByWriteId(writeId, callback) { _promiseComplete(Pad.findOne({ where: { writeId: writeId }, include: [ { model: View, as: "defaultView" } ] }), callback); } function createPad(padId, writeId, callback) { _promiseComplete(Pad.create({ id: padId, writeId: writeId }), callback); } function updatePadData(padId, data, callback) { async.waterfall([ function(next) { _promiseComplete(Pad.update(data, { where: { id: padId } }), next); }, function(affectedNumber, next) { getPadData(padId, next); } ], callback); } 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, callback) { var obj = conn.model(type).build(data); obj.padId = padId; _promiseComplete(obj.save(), callback); } function _createPadObjectWithData(type, padId, data, callback) { async.auto({ obj: function(next) { _createPadObject(type, padId, data, next); }, data: [ "obj", function(res, next) { if(data.data != null) { res.obj.data = data.data; res.obj.setDataValue("data", res.obj.data); // For JSON.stringify() _setObjectData(type, res.obj.id, data.data, next); } else { res.obj.data = { }; res.obj.setDataValue("data", res.obj.data); // For JSON.stringify() next(); } } ] }, function(err, res) { callback(err, res.obj); }); } function _updatePadObject(type, objId, data, callback) { async.waterfall([ function(next) { _promiseComplete(conn.model(type).update(data, { where: { id: objId } }), next); }, function(affectedCount, next) { _promiseComplete(conn.model(type).findById(objId), next); } ], callback); } function _updatePadObjectWithData(type, objId, data, callback) { async.auto({ obj: function(next) { _updatePadObject(type, objId, data, next); }, data: function(next) { if(data.data != null) _setObjectData(type, objId, data.data, next); else next(); }, getData: function(next) { if(data.data == null) _getObjectData(type, objId, next); else next(); } }, function(err, res) { if(err) return callback(err); res.obj.data = (data.data != null ? data.data : res.getData); res.obj.setDataValue("data", res.obj.data); // For JSON.stringify() callback(null, res.obj); }); } function _deletePadObject(type, objId, callback) { conn.model(type).findById(objId).then(function(obj) { return obj.destroy().then(function() { callback(null, obj); }); }).catch(callback); } function _deletePadObjectWithData(type, objId, callback) { async.series([ function(next) { _setObjectData(type, objId, { }, next); }, function(next) { _deletePadObject(type, objId, callback); // Pass on object to callback } ], callback); } 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