jsharmony
Version:
Rapid Application Development (RAD) Platform for Node.js Database Application Development
668 lines (608 loc) • 33.1 kB
JavaScript
/*
Copyright 2017 apHarmony
This file is part of jsHarmony.
jsHarmony is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
jsHarmony is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this package. If not, see <http://www.gnu.org/licenses/>.
*/
var Helper = require('./lib/Helper.js');
var _ = require('lodash');
var async = require('async');
var HelperFS = require('./lib/HelperFS.js');
var crypto = require('crypto');
module.exports = exports = {};
exports.getModelForm = function (req, res, fullmodelid, Q, P, form_m) {
var _this = this;
var model = this.jsh.getModel(req, fullmodelid);
if (!Helper.hasModelAction(req, model, 'B')) { Helper.GenError(req, res, -11, _this._tP('Invalid Model Access for @fullmodelid', { fullmodelid })); return; }
if (model.unbound && !model._sysconfig.unbound_meta) { Helper.GenError(req, res, -11, 'Cannot run database queries on unbound models'); return; }
var fieldlist = this.getFieldNames(req, model.fields, 'B');
var filelist = this.getFileFieldNames(req, model.fields, 'B');
var keylist = this.getKeyNames(model.fields);
var foreignkeylist = this.getFieldNames(req, model.fields, 'F');
var crumbfieldlist = this.getFieldNames(req, model.fields, 'C');
var allfieldslist = _.union(keylist, fieldlist);
var encryptedfields = this.getEncryptedFields(req, model.fields, 'B');
var lovkeylist = this.getFieldNamesWithProp(model.fields, 'lovkey');
var db = _this.jsh.getModelDB(req, fullmodelid);
var dbcontext = _this.jsh.getDBContext(req, model, db);
if ((encryptedfields.length > 0) && !(req.secure) && (!_this.jsh.Config.system_settings.allow_insecure_http_encryption)) { Helper.GenError(req, res, -51, 'Encrypted fields require HTTPS connection'); return; }
var is_insert = false;
var is_browse = false;
var selecttype = 'single';
if (typeof form_m == 'undefined') form_m = false;
if (form_m) {
is_insert = (('_action' in Q) && (Q['_action'] == 'insert'));
is_browse = (('_action' in Q) && (Q['_action'] == 'browse'));
//Check if multiple or single and validate parameters
if (_this.ParamCheck('Q', Q, _.map(keylist, function (key) { return '&' + key; }), false)) { /* Default */ }
else if (_this.ParamCheck('Q', Q, _.union(_.map(foreignkeylist, function (field) { return '|' + field; }), ['|_action']), false)) {
selecttype = 'multiple';
}
else {
//Display missing keys
_this.ParamCheck('Q', Q, _.map(keylist, function (key) { return '&' + key; }), true);
Helper.GenError(req, res, -4, 'Invalid Parameters');
return;
}
}
else {
is_insert = (_.isEmpty(Q)) || (Q && ('_action' in Q) && (Q['_action'] == 'insert'));
is_browse = (Q && ('_action' in Q) && (Q['_action'] == 'browse'));
if (!_this.ParamCheck('Q', Q, _.union(_.map(keylist, function (key) { return '|' + key; }), ['|_action']), false)) {
is_insert = true;
if (!_this.ParamCheck('Q', Q, _.union(_.map(_.union(crumbfieldlist, lovkeylist), function (field) { return '|' + field; }), ['|_action']), true)) {
Helper.GenError(req, res, -4, 'Invalid Parameters'); return;
}
}
}
if (!_this.ParamCheck('P', P, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
var nokey = (('nokey' in model) && (model.nokey));
if (nokey) is_insert = false;
if (model.unbound) is_insert = false;
var sql_ptypes = [];
var sql_params = {};
var verrors = {};
var allfields = this.getFieldsByName(model.fields, allfieldslist);
var sql_allkeys = [];
var datalockqueries = [];
var sortfields = [];
//Add Keys to where
if (!nokey) {
if ((selecttype == 'single')) _.each(keylist, function (val) { sql_allkeys.push(val); });
else if (selecttype == 'multiple') _.each(foreignkeylist, function (val) { if (val in Q) sql_allkeys.push(val); });
}
var sql_allkeyfields = this.getFieldsByName(model.fields, sql_allkeys);
//Add DataLock parameters to SQL
var skipDataLocks = [];
if(is_insert) skipDataLocks = skipDataLocks.concat(keylist);
if (!is_insert && !model.unbound) this.getDataLockSQL(req, model, model.fields, sql_ptypes, sql_params, verrors, function (datalockquery) { datalockqueries.push(datalockquery); }, null, fullmodelid, { skipDataLocks: skipDataLocks });
if (selecttype == 'multiple') {
var dsort = new Array();
if ('sort' in model) dsort = model['sort'];
var unsortedkeys = keylist.slice();
_.each(dsort, function (val) {
if (!_.isString(val)) throw new Error('Invalid sort string');
if (val.length < 2) throw new Error('Invalid sort string');
var sortfield = val.substring(1);
var sortdir = val[0];
if (sortdir == 'v') sortdir = 'desc';
else if (sortdir == '^') sortdir = 'asc';
else throw new Error('Invalid sort string');
if (!_.includes(allfieldslist, sortfield)) throw new Error('Invalid sort field ' + sortfield);
var field = _this.getFieldByName(model.fields, sortfield);
sortfields.push({ 'field': sortfield, 'dir': sortdir, 'sql': (field.sqlsort || '') });
if (_.includes(unsortedkeys, sortfield)) unsortedkeys = _.without(unsortedkeys, sortfield);
});
if (unsortedkeys.length > 0) _.each(unsortedkeys, function (keyname) {
sortfields.push({ 'field': keyname, 'dir': 'asc', 'sql': '' });
});
}
var keys = [];
if (is_insert && !Helper.hasModelAction(req, model, 'I')) { Helper.GenError(req, res, -11, _this._tP('Invalid Model Access for @fullmodelid', { fullmodelid })); return; }
if (!is_insert && !nokey && !model.unbound) {
//Add dynamic parameters from query string
if (selecttype == 'single') keys = this.getKeys(model.fields);
else if (selecttype == 'multiple') keys = this.getFields(req, model.fields, 'F');
for (var i = 0; i < keys.length; i++) {
var field = keys[i];
var fname = field.name;
if (fname in Q) {
var dbtype = _this.getDBType(field);
sql_ptypes.push(dbtype);
sql_params[fname] = _this.DeformatParam(field, Q[fname], verrors);
}
else if (selecttype == 'single') { _this.jsh.Log.warning('Missing parameter ' + fname); Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
}
if (selecttype == 'single') verrors = _.merge(verrors, model.xvalidate.Validate('K', sql_params));
else if (selecttype == 'multiple') verrors = _.merge(verrors, model.xvalidate.Validate('F', sql_params, undefined, undefined, undefined, { ignoreUndefined: true }));
if (!_.isEmpty(verrors)) { Helper.GenError(req, res, -2, verrors[''].join('\n')); return; }
}
//Return applicable drop-down lists
var dbtasks = [{},{}];
if (!is_insert && !model.unbound) dbtasks[0][fullmodelid] = function (dbtrans, callback) {
var sql = db.sql.getModelForm(_this.jsh, model, selecttype, allfields, sql_allkeyfields, datalockqueries, sortfields);
var dbfunc = db.Row;
if (selecttype == 'multiple') dbfunc = db.Recordset;
dbfunc.call(db, dbcontext, sql, sql_ptypes, sql_params, dbtrans, function (err, rslt, stats) {
if ((err == null) && (rslt == null)) err = Helper.NewError('Record not found', -1);
if (err != null) { err.model = model; err.sql = sql; }
else {
if (stats) stats.model = model;
if ((rslt != null) && (selecttype == 'single') && (keylist.length == 1)) {
var keyval = sql_params[keylist[0]];
//Decrypt encrypted fields
if (encryptedfields.length > 0) {
if (keys.length != 1) throw new Error('Encryption requires one key');
for(let j = 0; j < encryptedfields.length; j++){
let encryptedfield = encryptedfields[j];
var encval = rslt[encryptedfield.name];
if (encval == null) continue;
if (encryptedfield.type == 'encascii') {
if (!(encryptedfield.password in _this.jsh.Config.passwords)) throw new Error('Encryption password not defined.');
try{
var password = _this.jsh.Config.passwords[encryptedfield.password];
rslt[encryptedfield.name] = Helper.decrypt(password.algorithm, keyval + password.key, encval);
}
catch(ex){
_this.jsh.Log.error(ex);
return callback(new Error('Error decrypting data'));
}
}
}
}
//Verify files exist on disk
if (filelist.length > 0) {
//For each file
var filerslt = {};
async.each(filelist, function (file, filecallback) {
var filefield = _this.getFieldByName(model.fields, file);
var fpath = _this.jsh.Config.datadir + filefield.controlparams.data_folder + '/' + (filefield.controlparams.data_file_prefix||file) + '_' + keyval;
if (filefield.controlparams._data_file_has_extension) fpath += '%%%EXT%%%';
HelperFS.getExtFileName(fpath, function(err, filename){
if(err){
filerslt[file] = false;
return filecallback(null);
}
HelperFS.exists(filename, function (exists) {
filerslt[file] = exists;
return filecallback(null);
});
});
}, function (err) {
if (err != null){
_this.jsh.Log.error(err);
return callback(Helper.NewError('Error performing file operation', -99999));
}
_.merge(rslt, filerslt);
callback(null, rslt, stats);
});
return;
}
}
else if ((rslt != null) && (selecttype == 'multiple')) {
if (filelist.length > 0) { throw new Error('Files not supported on FORM-M'); }
if (encryptedfields.length > 0) { throw new Error('Encryption not supported on FORM-M'); }
}
}
callback(err, rslt, stats);
});
};
else if (is_insert && (selecttype == 'multiple')) {
dbtasks[0][fullmodelid] = function (dbtrans, callback) {
var rslt = [];
callback(null, rslt);
};
}
//Default Values
var has_unbound_field_with_default = false;
_.each(model.fields, function(field){ if(field.unbound && field.default) has_unbound_field_with_default = true; });
if (is_insert || (selecttype == 'multiple') || has_unbound_field_with_default) {
if(_this.addDefaultTasks(req, res, model, Q, dbtasks[1])===false) return;
}
//Titles
var targetperm = 'U';
if(is_browse) targetperm = 'B';
else if(is_insert) targetperm = 'I';
else if(selecttype == 'multiple') targetperm = 'U';
if(_this.addTitleTasks(req, res, model, Q, dbtasks[1], targetperm)===false) return;
//Breadcrumbs
if(_this.addBreadcrumbTasks(req, res, model, Q, dbtasks[1], targetperm)===false) return;
//LOV
if(_this.addLOVTasks(req, res, model, Q, dbtasks[1], { action: targetperm })===false) return;
if (!_.isEmpty(verrors)) { Helper.GenError(req, res, -2, verrors[''].join('\n')); return; }
return dbtasks;
};
exports.putModelForm = function (req, res, fullmodelid, Q, P, onComplete) {
var _this = this;
var model = this.jsh.getModel(req, fullmodelid);
if (!Helper.hasModelAction(req, model, 'I')) { Helper.GenError(req, res, -11, _this._tP('Invalid Model Access for @fullmodelid', { fullmodelid })); return; }
var fieldlist = this.getFieldNames(req, model.fields, 'I');
var filelist = this.getFileFieldNames(req, model.fields, 'I');
var encryptedfields = this.getEncryptedFields(req, model.fields, 'I');
if ((encryptedfields.length > 0) && !(req.secure) && (!_this.jsh.Config.system_settings.allow_insecure_http_encryption)) { Helper.GenError(req, res, -51, 'Encrypted fields require HTTPS connection'); return; }
var db = _this.jsh.getModelDB(req, fullmodelid);
var dbcontext = _this.jsh.getDBContext(req, model, db);
var Pcheck = _.map(fieldlist, function (field) { return '&' + field; });
Pcheck = Pcheck.concat(_.map(filelist, function (file) { return '|' + file; }));
if (!_this.ParamCheck('Q', Q, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!_this.ParamCheck('P', P, Pcheck)) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
//Add to P
//Add to fieldlist
//Add extra parameters to sql
//getFieldsByName
var sql_ptypes = [];
var sql_params = {};
var sql_extfields = [];
var sql_extvalues = [];
var verrors = {};
var param_datalocks = [];
var vfiles = {};
var fileops = [];
var enc_sql_ptypes = [];
var enc_sql_params = {};
var enc_datalockqueries = [];
var hashfields = {};
async.eachSeries(filelist, _this.ProcessFileParams.bind(_this, req, res, model, P, fieldlist, sql_extfields, sql_extvalues, fileops, vfiles), function (err) {
if (err != null) return;
//Remove any encrypted fields from the initial update
_.each(encryptedfields, function (field) {
_.remove(fieldlist, function (val) { return val == field.name; });
if (field.type == 'encascii') {
if ('hash' in field) {
var hashfield = _.find(model.fields, function (xfield) { return xfield.name == field.hash; });
if (typeof hashfield == 'undefined') throw new Error('Field ' + field.name + ' hash is not defined.');
hashfields[field.name] = hashfield;
}
}
});
var keys = _this.getKeys(model.fields);
//Set up Encryption SQL and Parameters
if (encryptedfields.length > 0) {
//Add dynamic keys to parameters
_.each(keys, function (key) {
var dbtype = _this.getDBType(key);
enc_sql_ptypes.push(dbtype);
enc_sql_params[key.name] = '%%%' + key.name + '%%%';
});
//Add Encryption SQL Parameters
_.each(encryptedfields, function (field) {
enc_sql_ptypes.push(_this.getDBType(field));
var fname = field.name;
if (fname in P) {
enc_sql_params[fname] = _this.DeformatParam(field, P[fname], verrors);
if ('hash' in field) {
enc_sql_ptypes.push(_this.getDBType(hashfields[field.name]));
enc_sql_params[hashfields[field.name].name] = null;
}
}
else throw new Error('Missing parameter ' + fname);
});
//Add DataLock parameters to Encryption SQL
_this.getDataLockSQL(req, model, model.fields, enc_sql_ptypes, enc_sql_params, verrors, function (datalockquery) { enc_datalockqueries.push(datalockquery); });
}
var subs = [];
//Add fields from post
var fields = _this.getFieldsByName(model.fields, fieldlist);
if (fields.length == 0) return onComplete(null, {});
_.each(fields, function (field) {
var fname = field.name;
if (fname in P) {
var dbtype = _this.getDBType(field);
sql_ptypes.push(dbtype);
if (P[fname] == '%%%' + fname + '%%%') { subs.push(fname); P[fname] = ''; }
sql_params[fname] = _this.DeformatParam(field, P[fname], verrors);
//Add PreCheck, if type='F'
if (Helper.hasAction(field.actions, 'F')) {
_this.getDataLockSQL(req, model, model.fields, sql_ptypes, sql_params, verrors, function (datalockquery, dfield) {
if (dfield != field) return false;
param_datalocks.push({ pname: fname, datalockquery: datalockquery, field: dfield });
return true;
},undefined,model.id + ': '+fname);
}
}
else throw new Error('Missing parameter ' + fname);
});
var ignore_subs = _.map(subs, function(val){ return '_obj.'+val; });
verrors = _.merge(verrors, model.xvalidate.Validate('I', _.merge(vfiles, enc_sql_params, sql_params), '', ignore_subs));
if (!_.isEmpty(verrors)) { Helper.GenError(req, res, -2, verrors[''].join('\n')); return; }
var dbsql = db.sql.putModelForm(_this.jsh, model, fields, keys, sql_extfields, sql_extvalues, encryptedfields, hashfields, enc_datalockqueries, param_datalocks);
_.each(subs, function (fname) { sql_params[fname] = '%%%' + fname + '%%%'; });
var dbtasks = {};
var sql_rslt = null;
dbtasks[fullmodelid] = function (dbtrans, callback, transtbl) {
sql_params = _this.ApplyTransTblEscapedParameters(sql_params, transtbl);
db.Row(dbcontext, dbsql.sql, sql_ptypes, sql_params, dbtrans, function (err, rslt, stats) {
if (stats) stats.model = model;
if ((err == null) && (rslt != null) && (_this.jsh.map.rowcount in rslt) && (rslt[_this.jsh.map.rowcount] == 0)) err = Helper.NewError('No records affected', -3, stats);
if (err != null) { err.model = model; err.sql = dbsql.sql; }
else {
if(model.onsqlinserted) sql_rslt = rslt;
if (fileops.length > 0) {
//Move files, if applicable
var keyval = '';
if (keys.length == 1) keyval = rslt[keys[0].name];
else throw new Error('File uploads require one key');
return _this.ProcessFileOperations(keyval, fileops, rslt, stats, callback);
}
}
callback(err, rslt, stats);
});
};
if (encryptedfields.length > 0) {
if (keys.length != 1) throw new Error('Encryption requires one key');
dbtasks['enc_' + fullmodelid] = function (dbtrans, callback, transtbl) {
if (typeof dbtrans == 'undefined') return callback(Helper.NewError('Encryption must be executed within a transaction', -50), null);
enc_sql_params = _this.ApplyTransTblEscapedParameters(enc_sql_params, transtbl);
var transvars = _this.getTransVars(transtbl);
var keyval = transvars[keys[0].name];
//Encrypt Data
_.each(encryptedfields, function (field) {
var clearval = enc_sql_params[field.name];
if (field.type == 'encascii') {
if (clearval.length == 0) {
enc_sql_params[field.name] = null;
if ('hash' in field) { enc_sql_params[hashfields[field.name].name] = null; }
}
else {
if (!(field.password in _this.jsh.Config.passwords)) throw new Error('Encryption password not defined.');
var password = _this.jsh.Config.passwords[field.password];
enc_sql_params[field.name] = Helper.encrypt(password.algorithm, keyval + password.key, clearval);
if ('hash' in field) {
var hashfield = hashfields[field.name];
if (!(hashfield.salt in _this.jsh.Config.salts)) throw new Error('Hash salt not defined.');
enc_sql_params[hashfield.name] = crypto.createHash('sha1').update(clearval + _this.jsh.Config.salts[hashfield.salt]).digest();
}
}
}
});
db.Row(dbcontext, dbsql.enc_sql, enc_sql_ptypes, enc_sql_params, dbtrans, function (err, rslt, stats) {
if (stats) stats.model = model;
if ((err == null) && (rslt != null) && (_this.jsh.map.rowcount in rslt) && (rslt[_this.jsh.map.rowcount] == 0)) err = Helper.NewError('No records affected', -3, stats);
if (err != null) { err.model = model; err.sql = dbsql.enc_sql; }
callback(err, rslt, stats);
});
};
}
if (fileops.length > 0) dbtasks['_POSTPROCESS'] = function (callback) {
_this.ProcessFileOperationsDone(fileops, callback);
};
if(model.onsqlinserted) dbtasks['_ONSQLINSERTED_POSTPROCESS'] = function (callback, rslt) {
model.onsqlinserted(callback, req, res, sql_params, sql_rslt, require, _this.jsh, model.id);
};
return onComplete(null, dbtasks);
});
};
exports.postModelForm = function (req, res, fullmodelid, Q, P, onComplete) {
var _this = this;
if (!this.jsh.hasModel(req, fullmodelid)) throw new Error('Error: Model ' + fullmodelid + ' not found in collection.');
var model = this.jsh.getModel(req, fullmodelid);
if (!Helper.hasModelAction(req, model, 'U')) { Helper.GenError(req, res, -11, _this._tP('Invalid Model Access for @fullmodelid', { fullmodelid })); return; }
var fieldlist = this.getFieldNames(req, model.fields, 'U');
var keylist = this.getKeyNames(model.fields);
var filelist = this.getFileFieldNames(req, model.fields, 'U');
var encryptedfields = this.getEncryptedFields(req, model.fields, 'U');
if ((encryptedfields.length > 0) && !(req.secure) && (!_this.jsh.Config.system_settings.allow_insecure_http_encryption)) { Helper.GenError(req, res, -51, 'Encrypted fields require HTTPS connection'); return; }
var db = _this.jsh.getModelDB(req, fullmodelid);
var dbcontext = _this.jsh.getDBContext(req, model, db);
var Pcheck = _.map(fieldlist, function (field) { return '&' + field; });
Pcheck = Pcheck.concat(_.map(filelist, function (file) { return '|' + file; }));
if (!_this.ParamCheck('Q', Q, _.map(keylist, function (key) { return '&' + key; }))) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!_this.ParamCheck('P', P, Pcheck)) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
var sql_ptypes = [];
var sql_params = {};
var sql_extfields = [];
var sql_extvalues = [];
var verrors = {};
var vfiles = {};
var vignorefiles = [];
var fileops = [];
var hashfields = {};
var datalockqueries = [];
var param_datalocks = [];
async.eachSeries(filelist, _this.ProcessFileParams.bind(_this, req, res, model, P, fieldlist, sql_extfields, sql_extvalues, fileops, vfiles), function (err) {
if (err != null) return;
_.each(filelist, function (file) { if (!(file in P)) vignorefiles.push('_obj.' + file); });
_.each(encryptedfields, function (field) {
if (field.type == 'encascii') {
if ('hash' in field) {
let hashfield = _.find(model.fields, function (xfield) { return xfield.name == field.hash; });
if (typeof hashfield == 'undefined') throw new Error('Field ' + field.name + ' hash is not defined.');
hashfields[field.name] = hashfield;
}
}
});
//Add key from query string
var keys = _this.getKeys(model.fields);
_.each(keys, function (field) {
var fname = field.name;
if (fname in Q) {
var dbtype = _this.getDBType(field);
sql_ptypes.push(dbtype);
sql_params[fname] = _this.DeformatParam(field, Q[fname], verrors);
}
else throw new Error('Missing parameter ' + fname);
});
//Remove blank password fields from fields array
var fields = _this.getFieldsByName(model.fields, fieldlist, function(field){
if((field.name in P)&&
(field.control=='password')&&
((typeof P[field.name]=='undefined')||(P[field.name]===null)||(P[field.name]===''))&&
!(field.controlparams && field.controlparams.update_when_blank))
return false;
return true;
});
var dbtasks = {};
var sql_rslt = null;
//Add fields from post
if ((fields.length > 0)||(sql_extfields.length > 0)||model.sqlupdate){
_.each(fields, function (field) {
var fname = field.name;
if(field.sqlupdate==='') return;
if (fname in P) {
var dbtype = _this.getDBType(field);
sql_ptypes.push(dbtype);
sql_params[fname] = _this.DeformatParam(field, P[fname], verrors);
//Add PreCheck, if type='F'
if (Helper.hasAction(field.actions, 'F')) {
_this.getDataLockSQL(req, model, model.fields, sql_ptypes, sql_params, verrors, function (datalockquery, dfield) {
if (dfield != field) return false;
param_datalocks.push({ pname: fname, datalockquery: datalockquery, field: dfield });
return true;
});
}
}
else throw new Error('Missing parameter ' + fname);
});
//Add DataLock parameters to SQL
_this.getDataLockSQL(req, model, model.fields, sql_ptypes, sql_params, verrors, function (datalockquery) { datalockqueries.push(datalockquery); });
verrors = _.merge(verrors, model.xvalidate.Validate('UK', _.merge(vfiles, sql_params), '', vignorefiles, req._roles));
if (!_.isEmpty(verrors)) { Helper.GenError(req, res, -2, verrors[''].join('\n')); return; }
if (encryptedfields.length > 0) {
//Add encrypted field
var keyval = '';
if (keys.length == 1) keyval = sql_params[keys[0].name];
else throw new Error('File uploads require one key');
_.each(encryptedfields, function (field) {
var clearval = sql_params[field.name];
if (clearval.length == 0) {
sql_params[field.name] = null;
if ('hash' in field) {
let hashfield = hashfields[field.name];
sql_ptypes.push(_this.getDBType(hashfield));
sql_params[hashfield.name] = null;
}
}
else {
if (field.type == 'encascii') {
if (!(field.password in _this.jsh.Config.passwords)) throw new Error('Encryption password not defined.');
var password = _this.jsh.Config.passwords[field.password];
sql_params[field.name] = Helper.encrypt(password.algorithm, keyval + password.key, clearval);
if ('hash' in field) {
let hashfield = hashfields[field.name];
if (!(hashfield.salt in _this.jsh.Config.salts)) throw new Error('Hash salt not defined.');
sql_ptypes.push(_this.getDBType(hashfield));
sql_params[hashfield.name] = crypto.createHash('sha1').update(clearval + _this.jsh.Config.salts[hashfield.salt]).digest();
}
}
}
});
}
var sql = db.sql.postModelForm(_this.jsh, model, fields, keys, sql_extfields, sql_extvalues, hashfields, param_datalocks, datalockqueries);
dbtasks[fullmodelid] = function (dbtrans, callback, transtbl) {
sql_params = _this.ApplyTransTblEscapedParameters(sql_params, transtbl);
db.Row(dbcontext, sql, sql_ptypes, sql_params, dbtrans, function (err, rslt, stats) {
if (stats) stats.model = model;
if ((err == null) && (rslt != null) && (_this.jsh.map.rowcount in rslt) && (rslt[_this.jsh.map.rowcount] == 0)) err = Helper.NewError('No records affected', -3, stats);
if (err != null) { err.model = model; err.sql = sql; }
else {
if(model.onsqlupdated) sql_rslt = rslt;
if (fileops.length > 0) {
//Set keyval and move files, if applicable
var keyval = '';
if (keys.length == 1) keyval = sql_params[keys[0].name];
else throw new Error('File uploads require one key');
return _this.ProcessFileOperations(keyval, fileops, rslt, stats, callback);
}
}
callback(err, rslt, stats);
});
};
}
else if(fileops.length > 0){
verrors = _.merge(verrors, model.xvalidate.Validate('UK', _.merge(vfiles, sql_params), '', vignorefiles, req._roles));
if (!_.isEmpty(verrors)) { Helper.GenError(req, res, -2, verrors[''].join('\n')); return; }
dbtasks[fullmodelid] = function(dbtrans, callback, transtbl){
sql_params = _this.ApplyTransTblEscapedParameters(sql_params, transtbl);
var keyval = '';
if (keys.length == 1) keyval = sql_params[keys[0].name];
else throw new Error('File uploads require one key');
return _this.ProcessFileOperations(keyval, fileops, {}, {}, callback);
};
}
if (fileops.length > 0) dbtasks['_POSTPROCESS'] = function (callback) {
_this.ProcessFileOperationsDone(fileops, callback);
};
if(model.onsqlupdated) dbtasks['_ONSQLUPDATED_POSTPROCESS'] = function (callback) {
model.onsqlupdated(callback, req, res, sql_params, sql_rslt, require, _this.jsh, model.id);
};
return onComplete(null, dbtasks);
});
};
exports.deleteModelForm = function (req, res, fullmodelid, Q, P, onComplete) {
if (!this.jsh.hasModel(req, fullmodelid)) throw new Error('Error: Model ' + fullmodelid + ' not found in collection.');
var _this = this;
var model = this.jsh.getModel(req, fullmodelid);
if (!Helper.hasModelAction(req, model, 'D')) { Helper.GenError(req, res, -11, _this._tP('Invalid Model Access for @fullmodelid', { fullmodelid })); return; }
var keylist = this.getKeyNames(model.fields);
var fieldlist = this.getFieldNames(req, model.fields, 'D');
var filelist = this.getFileFieldNames(req, model.fields, '*');
var db = _this.jsh.getModelDB(req, fullmodelid);
var dbcontext = _this.jsh.getDBContext(req, model, db);
var Qcheck = _.map(keylist, function (key) { return '&' + key; });
Qcheck = Qcheck.concat(_.map(fieldlist, function (field) { return '|' + field; }));
if (!_this.ParamCheck('Q', Q, Qcheck)) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
if (!_this.ParamCheck('P', P, [])) { Helper.GenError(req, res, -4, 'Invalid Parameters'); return; }
var sql_ptypes = [];
var sql_params = {};
var verrors = {};
var datalockqueries = [];
var keys = _this.getKeys(model.fields);
_.each(keys, function (field) {
var fname = field.name;
if (fname in Q) {
var dbtype = _this.getDBType(field);
sql_ptypes.push(dbtype);
sql_params[fname] = _this.DeformatParam(field, Q[fname], verrors);
}
else throw new Error('Missing parameter ' + fname);
});
//Add DataLock parameters to SQL
_this.getDataLockSQL(req, model, model.fields, sql_ptypes, sql_params, verrors, function (datalockquery) { datalockqueries.push(datalockquery); });
verrors = _.merge(verrors, model.xvalidate.Validate('K', sql_params));
if (!_.isEmpty(verrors)) { Helper.GenError(req, res, -2, verrors[''].join('\n')); return; }
var sql = db.sql.deleteModelForm(_this.jsh, model, keys, datalockqueries);
var dbtasks = {};
var sql_rslt = null;
dbtasks[fullmodelid] = function (dbtrans, callback) {
db.Row(dbcontext, sql, sql_ptypes, sql_params, dbtrans, function (err, rslt, stats) {
if (stats) stats.model = model;
if ((err == null) && (rslt != null) && (_this.jsh.map.rowcount in rslt) && (rslt[_this.jsh.map.rowcount] == 0)) err = Helper.NewError('No records affected', -3, stats);
if (err != null) { err.model = model; err.sql = sql; }
if(model.onsqldeleted) sql_rslt = rslt;
callback(err, rslt, stats);
});
};
//Add post-processing task to delete any files
if (filelist.length > 0) {
var keyval = '';
if (keys.length == 1) keyval = sql_params[keys[0].name];
else throw new Error('File uploads require one key');
if ((typeof keyval == 'undefined') || !keyval) return Helper.GenError(req, res, -13, 'Invalid file key');
var fileops = [];
_.each(filelist, function (file) {
var filefield = _this.getFieldByName(model.fields, file);
//Delete file in post-processing
fileops.push({ op: 'move', src: _this.jsh.Config.datadir + filefield.controlparams.data_folder + '/' + (filefield.controlparams.data_file_prefix||file) + '_' + keyval + ((filefield.controlparams._data_file_has_extension)?'%%%EXT%%%':''), dst: '' });
//Delete thumbnails in post-processing
if (filefield.controlparams.thumbnails) for (var tname in filefield.controlparams.thumbnails) {
fileops.push({ op: 'move', src: _this.jsh.Config.datadir + filefield.controlparams.data_folder + '/' + (filefield.controlparams.data_file_prefix||filefield.name) + '_' + tname + '_' + keyval + ((filefield.controlparams._data_file_has_extension)?'%%%EXT%%%':''), dst: '' });
}
});
dbtasks['_POSTPROCESS'] = function (callback) {
_this.ProcessFileOperationsDone(fileops, callback);
};
}
if(model.onsqldeleted) dbtasks['_ONSQLDELETED_POSTPROCESS'] = function (callback, rslt) {
model.onsqldeleted(callback, req, res, sql_params, sql_rslt, require, _this.jsh, model.id);
};
return onComplete(null, dbtasks);
};
return module.exports;