backend-plus 2.7.0-beta.0 → 2.7.0-beta.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,6 +29,7 @@ var packagejson = require(process.cwd()+'/package.json');
29
29
  var stackTrace = require('stack-trace');
30
30
  var locatePath = require('@upgraded/locate-path');
31
31
  var jsYaml = require('js-yaml');
32
+ var tabPlus = require('tab-plus');
32
33
  var nodemailer = require('nodemailer');
33
34
  var os = require('os');
34
35
  const cors = require('cors');
@@ -73,7 +74,7 @@ if(dashDashDir>0){
73
74
  console.log('cwd',process.cwd());
74
75
  }
75
76
 
76
- /**
77
+ /**
77
78
  * @param {string} text
78
79
  * @return {string}
79
80
  */
@@ -81,14 +82,14 @@ function md5(text){
81
82
  return crypto.createHash('md5').update(text).digest('hex');
82
83
  }
83
84
 
84
- const DEFAULT_ITERATIONS = 4096;
85
+ const DEFAULT_ITERATIONS = 4096;
85
86
  const HASH_ALGORITHM = 'sha256';
86
87
 
87
88
  // primera version de scram sha 256. El Verifier completo tenía 64 bytes.
88
- const LEGACY_KEY_LENGTH = 64;
89
+ const LEGACY_KEY_LENGTH = 64;
89
90
 
90
91
  // Formato nuevo/PG-Compatible: El Client Key tiene 32 bytes (SHA-256).
91
- const SCRAM_KEY_LENGTH = 32;
92
+ const SCRAM_KEY_LENGTH = 32;
92
93
 
93
94
  const bufferToBase64 = (buffer) => buffer.toString('base64');
94
95
 
@@ -96,31 +97,31 @@ const bufferToBase64 = (buffer) => buffer.toString('base64');
96
97
  * Función central para PBKDF2 (Acepta KEY_LEN para compatibilidad).
97
98
  * Devuelve la clave derivada con la longitud especificada.
98
99
  */
99
- async function deriveKey(password, saltBuffer, iterations, keyLength) {
100
+ async function deriveKey(password, saltBuffer, iterations, keyLength) {
100
101
  return new Promise((resolve, reject) => {
101
102
  crypto.pbkdf2(
102
- password,
103
+ password,
103
104
  saltBuffer,
104
- iterations,
105
+ iterations,
105
106
  keyLength,
106
- HASH_ALGORITHM,
107
+ HASH_ALGORITHM,
107
108
  (err, derivedKey) => {
108
109
  if (err) return reject(err);
109
- resolve(derivedKey);
110
+ resolve(derivedKey);
110
111
  }
111
112
  );
112
113
  });
113
114
  }
114
115
 
115
116
  async function generateScramVerifier(password) {
116
- const saltBuffer = crypto.randomBytes(16);
117
+ const saltBuffer = crypto.randomBytes(16);
117
118
  const saltBase64 = bufferToBase64(saltBuffer);
118
119
 
119
120
  const Hi = await deriveKey(
120
- password,
121
- saltBuffer,
121
+ password,
122
+ saltBuffer,
122
123
  DEFAULT_ITERATIONS,
123
- SCRAM_KEY_LENGTH
124
+ SCRAM_KEY_LENGTH
124
125
  );
125
126
  const clientKey = crypto.createHmac('sha256', Hi).update('Client Key').digest();
126
127
  const serverKey = crypto.createHmac('sha256', Hi).update('Server Key').digest();
@@ -144,27 +145,27 @@ async function verifyScramPG(password, storedScramString) {
144
145
  if (!storedScramString.startsWith('SCRAM-SHA-256$')) {
145
146
  return false;
146
147
  }
147
-
148
+
148
149
  const parts = storedScramString.split('$');
149
150
  if (parts.length !== 3) {
150
151
  return false;
151
152
  }
152
-
153
+
153
154
  const [storedIterations, saltBase64] = parts[1].split(':');
154
155
  const [storedKeyBase64, serverKeyBase64] = parts[2].split(':'); // Asume StoredKey y ServerKey
155
156
 
156
157
  if (!saltBase64 || !storedKeyBase64 || !serverKeyBase64 || isNaN(parseInt(storedIterations))) {
157
158
  return false;
158
159
  }
159
-
160
+
160
161
  const iterations = parseInt(storedIterations);
161
162
  const saltBuffer = Buffer.from(saltBase64, 'base64');
162
163
  const storedKeyBuffer = Buffer.from(storedKeyBase64, 'base64');
163
164
  const serverKeyBuffer = Buffer.from(serverKeyBase64, 'base64');
164
165
 
165
166
  const Hi = await deriveKey(
166
- password,
167
- saltBuffer,
167
+ password,
168
+ saltBuffer,
168
169
  iterations,
169
170
  SCRAM_KEY_LENGTH
170
171
  );
@@ -203,21 +204,21 @@ async function verifyScramLegacy(password, storedScramString) {
203
204
  if (parts.length !== 3) {
204
205
  throw new Error('Formato SCRAM almacenado inválido.');
205
206
  }
206
-
207
+
207
208
  // Obtiene iteraciones y salt de la segunda parte (ej: '4096:SALT_BASE64')
208
209
  const [storedIterations, storedSalt] = parts[1].split(':');
209
210
  const storedVerifier = parts[2];
210
-
211
+
211
212
  if (!storedSalt || !storedVerifier || isNaN(parseInt(storedIterations))) {
212
213
  throw new Error('Datos de SCRAM incompletos o malformados.');
213
214
  }
214
-
215
+
215
216
  const iterations = parseInt(storedIterations);
216
217
 
217
218
  // Deriva la clave de la contraseña ingresada
218
219
  const generatedVerifierBuffer = await deriveKey(
219
- password,
220
- storedSalt,
220
+ password,
221
+ storedSalt,
221
222
  iterations,
222
223
  LEGACY_KEY_LENGTH
223
224
  );
@@ -313,7 +314,7 @@ AppBackend.prototype.configStaticConfig = function configStaticConfig(){
313
314
  min-version: 12
314
315
  fkOnUpdate: cascade
315
316
  max: 50
316
- log:
317
+ log:
317
318
  db:
318
319
  until: 2001-01-01 00:00
319
320
  last-error: false
@@ -326,7 +327,7 @@ AppBackend.prototype.configStaticConfig = function configStaticConfig(){
326
327
  "":
327
328
  local-path: for-client
328
329
  bin: {}
329
- client-setup:
330
+ client-setup:
330
331
  skin: ""
331
332
  lang: en
332
333
  version: 1.0
@@ -589,7 +590,7 @@ function MemoryPerodicallySaved(session){
589
590
  })}
590
591
 
591
592
  /**
592
- * @param {string} text
593
+ * @param {string} text
593
594
  */
594
595
  AppBackend.prototype.jsonPass = function jsonPass(text){
595
596
  return JSON.stringify(text,null,' ').replace(/\n(.*".*(pass|clave|secret).*":\s*").*(",?)\n/gi,'\n$1********$3\n');
@@ -751,7 +752,7 @@ AppBackend.prototype.start = function start(opts){
751
752
  while(iPosDbDump && iPosDbDump<process.argv.length && !process.argv[iPosDbDump].startsWith('--')){
752
753
  dumps.push(process.argv[iPosDbDump]);
753
754
  iPosDbDump++;
754
- }
755
+ }
755
756
  opts["dump-db"]={
756
757
  complete:!dumps.length,
757
758
  tableNames:dumps instanceof Array?dumps:null,
@@ -888,8 +889,8 @@ AppBackend.prototype.start = function start(opts){
888
889
  }
889
890
  be.db = pg;
890
891
  be.dbUserNameExpr="get_app_user()";
891
- be.dbUserRolExpr=`(select ${be.db.quoteIdent(be.config.login.rolFieldName)}
892
- from ${be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':''}${be.db.quoteIdent(be.config.login.table)}
892
+ be.dbUserRolExpr=`(select ${be.db.quoteIdent(be.config.login.rolFieldName)}
893
+ from ${be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':''}${be.db.quoteIdent(be.config.login.table)}
893
894
  where ${be.db.quoteIdent(be.config.login.userFieldName)} = ${be.dbUserNameExpr})`
894
895
  }).then(function(){
895
896
  if(opts["dump-db"]){
@@ -1032,6 +1033,7 @@ AppBackend.prototype.start = function start(opts){
1032
1033
  console.log(err);
1033
1034
  throw err;
1034
1035
  }
1036
+ await pg.setAllTypes(client);
1035
1037
  var data = await client.query("SELECT current_timestamp as cts").fetchUniqueRow();
1036
1038
  if(verboseStartup){
1037
1039
  console.log('NOW in Database',data.row.cts);
@@ -1047,7 +1049,7 @@ AppBackend.prototype.start = function start(opts){
1047
1049
  },function(){ /*OK, login.jade must not be here */ }),
1048
1050
  fs.stat('client/login.jade').then(function(){
1049
1051
  return Path.resolve(be.rootPath,'client/login');
1050
- },function(){
1052
+ },function(){
1051
1053
  return Path.join(__dirname,'../for-client/login');
1052
1054
  }).then(function(loginFile){
1053
1055
  be.config.login.plus.loginPageServe=function(req,res,next){
@@ -1164,7 +1166,7 @@ AppBackend.prototype.start = function start(opts){
1164
1166
  if(verboseStartup){
1165
1167
  console.log('-------------------');
1166
1168
  console.log(
1167
- 'be.config.login',
1169
+ 'be.config.login',
1168
1170
  be.jsonPass(be.config.login)
1169
1171
  );
1170
1172
  }
@@ -1193,7 +1195,7 @@ AppBackend.prototype.start = function start(opts){
1193
1195
  }
1194
1196
  });
1195
1197
  const updatePassword = async ({client, username, password, setUpdateDate, errorIfNoResult}) => {
1196
- const {table, passFieldName, userFieldName, passUpdatedAtFieldName, passAlgorithmFieldName, schema} = be.config.login;
1198
+ const {table, passFieldName, userFieldName, passUpdatedAtFieldName, passAlgorithmFieldName, schema} = be.config.login;
1197
1199
  const hashPass = await generateScramVerifier(password);
1198
1200
  let params = [username, hashPass];
1199
1201
  let setters = [`${be.db.quoteIdent(passFieldName)} = $2`];
@@ -1204,9 +1206,9 @@ AppBackend.prototype.start = function start(opts){
1204
1206
  if(setUpdateDate && passUpdatedAtFieldName){
1205
1207
  setters.push(`${be.db.quoteIdent(passUpdatedAtFieldName)} = current_timestamp`)
1206
1208
  }
1207
-
1209
+
1208
1210
  const result = await client.query(`
1209
- UPDATE ${(schema ? be.db.quoteIdent(schema) + '.' : '') + be.db.quoteIdent(table)}
1211
+ UPDATE ${(schema ? be.db.quoteIdent(schema) + '.' : '') + be.db.quoteIdent(table)}
1210
1212
  SET ${setters.join(', ')}
1211
1213
  WHERE ${be.db.quoteIdent(userFieldName)} = $1
1212
1214
  returning 1 as ok
@@ -1236,7 +1238,7 @@ AppBackend.prototype.start = function start(opts){
1236
1238
  [userFieldName]
1237
1239
  )
1238
1240
  ).concat(passFieldName);
1239
-
1241
+
1240
1242
  const sql = "SELECT "+infoFieldList.map(function(fieldOrPair){ return fieldOrPair.split(' as ').map(function(ident){ return be.db.quoteIdent(ident)}).join(' as '); })+
1241
1243
  ", "+be.config.login.activeClausule+" as active "+
1242
1244
  ", "+be.config.login.lockedClausule+" as locked "+
@@ -1264,7 +1266,7 @@ AppBackend.prototype.start = function start(opts){
1264
1266
  if(await verifyScramPG(password, user[passFieldName])){
1265
1267
  isScramValid = true;
1266
1268
  }
1267
-
1269
+
1268
1270
  // 2. Intento con formato Legacy (64 bytes)
1269
1271
  else if(await verifyScramLegacy(password, user[passFieldName])){
1270
1272
  isScramValid = true;
@@ -1274,7 +1276,7 @@ AppBackend.prototype.start = function start(opts){
1274
1276
  done(null,false,{message:be.messages.unlogged.login.userOrPassFail});
1275
1277
  return
1276
1278
  }
1277
- }else{
1279
+ }else{
1278
1280
  if (md5(password+username.toLowerCase()) === user[passFieldName]){
1279
1281
  needsMigration = true;
1280
1282
  }else{
@@ -1288,7 +1290,7 @@ AppBackend.prototype.start = function start(opts){
1288
1290
  if (be.config.log["pass-migration"]) console.log('Migración completada.');
1289
1291
  }
1290
1292
  }
1291
- //continua validando
1293
+ //continua validando
1292
1294
  if(data.rowCount==1){
1293
1295
  if(!data.row.active){
1294
1296
  done(null,false,{message:be.messages.unlogged.login.inactiveFail});
@@ -1344,7 +1346,7 @@ AppBackend.prototype.start = function start(opts){
1344
1346
  const data = await client.query(
1345
1347
  `SELECT *
1346
1348
  FROM ${(schema ? be.db.quoteIdent(schema) + '.' : '')}${be.db.quoteIdent(table)}
1347
- WHERE ${be.db.quoteIdent(userFieldName)} = $1`,
1349
+ WHERE ${be.db.quoteIdent(userFieldName)} = $1`,
1348
1350
  [username]
1349
1351
  ).fetchOneRowIfExists();
1350
1352
  if (data.rowCount !== 1) {
@@ -1360,7 +1362,7 @@ AppBackend.prototype.start = function start(opts){
1360
1362
  ok = await verifyScramLegacy(oldPassword, storedHash);
1361
1363
  }
1362
1364
  } else { //Intento MD5
1363
- const md5Hash = md5(oldPassword + username.toLowerCase());
1365
+ const md5Hash = md5(oldPassword + username.toLowerCase());
1364
1366
  ok = (md5Hash === storedHash);
1365
1367
  }
1366
1368
  if (!ok) {
@@ -1454,7 +1456,7 @@ AppBackend.prototype.start = function start(opts){
1454
1456
  to: be.config.mailer?.supervise?.to,
1455
1457
  subject: `npm start ${be.config["client-setup"]?.title || packagejson.name} ok ✔️`,
1456
1458
  text:`Inicio del servicio: ${new Date().toJSON()}
1457
-
1459
+
1458
1460
  Contexto: ${os.userInfo().username} ${process.cwd()}
1459
1461
  `
1460
1462
  }, {ignoreNoMailer:true, event:'restart-ok'})
@@ -1462,7 +1464,7 @@ AppBackend.prototype.start = function start(opts){
1462
1464
  if(err.dumping=='ok'){
1463
1465
  console.log('db struct dumped');
1464
1466
  process.exit(0);
1465
- return;
1467
+ return;
1466
1468
  }
1467
1469
  console.log('ERROR',err.stack || err);
1468
1470
  if(err.context){
@@ -1476,11 +1478,11 @@ AppBackend.prototype.start = function start(opts){
1476
1478
  to: be.config.mailer?.supervise?.to,
1477
1479
  subject: `npm start ${be.config["client-setup"]?.title || packagejson.name} fallido 🛑`,
1478
1480
  text:`Falla en el inicio del servicio: ${new Date().toJSON()}
1479
-
1481
+
1480
1482
  Contexto: ${os.userInfo().username} ${process.cwd()}
1481
1483
 
1482
1484
  Mensaje: ${err.message}
1483
-
1485
+
1484
1486
  ${err.stack}
1485
1487
  `
1486
1488
  }, {ignoreNoMailer:true, event:'restart-fail'})
@@ -1549,7 +1551,7 @@ AppBackend.prototype.checkDatabaseStructure = async function checkDatabaseStruct
1549
1551
  throw err;
1550
1552
  }
1551
1553
  try{
1552
- await client.query(`select ${be.db.quoteIdentList(be.config.login.forget.mailFields)}
1554
+ await client.query(`select ${be.db.quoteIdentList(be.config.login.forget.mailFields)}
1553
1555
  from ${be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':''}${be.config.login.table} limit 1`).fetchOneRowIfExists();
1554
1556
  }catch(err){
1555
1557
  var mensaje = `
@@ -1564,7 +1566,7 @@ AppBackend.prototype.checkDatabaseStructure = async function checkDatabaseStruct
1564
1566
  }
1565
1567
  }
1566
1568
  var bitacoraTableName = be.config.server.bitacoraTableName || 'bitacora';
1567
- var bitacoraId = await client.query(`SELECT data_type
1569
+ var bitacoraId = await client.query(`SELECT data_type
1568
1570
  FROM information_schema.columns
1569
1571
  WHERE /*table_schema = 'his' AND*/ table_name = '${bitacoraTableName}' AND column_name = 'id'
1570
1572
  `).fetchOneRowIfExists();
@@ -1609,9 +1611,9 @@ AppBackend.prototype.postConfig = function postConfig(){
1609
1611
  AppBackend.prototype.getContext = function getContext(req){
1610
1612
  var be = this;
1611
1613
  return {
1612
- be, user:req.user, session:req.session,
1613
- username:req.user && req.user[be.config.login.userFieldName],
1614
- machineId:req.machineId,
1614
+ be, user:req.user, session:req.session,
1615
+ username:req.user && req.user[be.config.login.userFieldName],
1616
+ machineId:req.machineId,
1615
1617
  navigator:(req.userAgent||{}).shortDescription||'?'
1616
1618
  };
1617
1619
  }
@@ -1641,7 +1643,7 @@ AppBackend.prototype.generateInsertSQL = function generateInsertSQL(schemaName,
1641
1643
  var cleanValues = [];
1642
1644
  for (var key in insertElement) {
1643
1645
  cleanKeys.push(db.quoteIdent(key));
1644
- cleanValues.push(db.quoteNullable(insertElement[key]));
1646
+ cleanValues.push(db.quoteNullable(insertElement[key]));
1645
1647
  }
1646
1648
  var sql = `INSERT INTO ${db.quoteIdent(schemaName)}.${db.quoteIdent(tableName)}
1647
1649
  (${cleanKeys.join(',')}) VALUES (${cleanValues.join(',')}) returning id`;
@@ -1659,7 +1661,7 @@ AppBackend.prototype.updateUpdateSQL = function updateUpdateSQL(schemaName, tabl
1659
1661
  for (var key in updateConditions) {
1660
1662
  filterPairs.push(be.db.quoteIdent(key) + " = " + be.db.quoteLiteral(updateConditions[key]));
1661
1663
  };
1662
- var sql = `UPDATE ${db.quoteIdent(schemaName)}.${db.quoteIdent(tableName)}
1664
+ var sql = `UPDATE ${db.quoteIdent(schemaName)}.${db.quoteIdent(tableName)}
1663
1665
  SET ${setPairs.join(',')}
1664
1666
  WHERE ${filterPairs.join(' AND ')}`;
1665
1667
  return sql;
@@ -1667,7 +1669,7 @@ AppBackend.prototype.updateUpdateSQL = function updateUpdateSQL(schemaName, tabl
1667
1669
 
1668
1670
 
1669
1671
  /** @param {boolean} forUnlogged */
1670
- AppBackend.prototype.addProcedureServices = function addProcedureServices(forUnlogged){
1672
+ AppBackend.prototype.addProcedureServices = function addProcedureServices(forUnlogged){
1671
1673
  var be = this;
1672
1674
  if(forUnlogged){
1673
1675
  var app = express();
@@ -1685,7 +1687,7 @@ AppBackend.prototype.addProcedureServices = function addProcedureServices(forUnl
1685
1687
  be.procedures = defs;
1686
1688
  be.clientSetup.procedure = be.procedure;
1687
1689
  app.get('/client-setup',async function(req, res, next){
1688
- if(forUnlogged && req.user){
1690
+ if(forUnlogged && req.user){
1689
1691
  // este pedido es para unlogged y está logueado, va al próximo
1690
1692
  next();
1691
1693
  }else{
@@ -1715,12 +1717,12 @@ AppBackend.prototype.addProcedureServices = function addProcedureServices(forUnl
1715
1717
  if(!isLowerIdent(procedureDef.action)){
1716
1718
  console.error('**** DEPRECATED ***** procedureDef action '+JSON.stringify(procedureDef.action)+' must be a Lower Ident');
1717
1719
  }
1718
- app[procedureDef.method]('/'+procedureDef.action,
1720
+ app[procedureDef.method]('/'+procedureDef.action,
1719
1721
  /**
1720
- *
1721
- * @param {Request} req
1722
- * @param {Response} res
1723
- * @param {import('express').NextFunction} next
1722
+ *
1723
+ * @param {Request} req
1724
+ * @param {Response} res
1725
+ * @param {import('express').NextFunction} next
1724
1726
  */
1725
1727
  async function(req, res, next){
1726
1728
  const BITACORA_SCHEMA = be.config.server.bitacoraSchema;
@@ -1773,9 +1775,9 @@ AppBackend.prototype.addProcedureServices = function addProcedureServices(forUnl
1773
1775
  init_date: initDatetimeString,
1774
1776
  };
1775
1777
  var getFinalStatusBitacoraElement = function getFinalStatusBitacoraElement(){
1776
- return {
1777
- end_date: getDatetimeString(),
1778
- end_status: status,
1778
+ return {
1779
+ end_date: getDatetimeString(),
1780
+ end_status: status,
1779
1781
  has_error: hasError
1780
1782
  }
1781
1783
  }
@@ -1805,12 +1807,12 @@ AppBackend.prototype.addProcedureServices = function addProcedureServices(forUnl
1805
1807
  var params = getParams();
1806
1808
  if(status){
1807
1809
  //terminó ejecucion
1808
- updateElement[procedureDef.bitacora.targetTableBitacoraFields.end_date] = getDatetimeString();
1810
+ updateElement[procedureDef.bitacora.targetTableBitacoraFields.end_date] = getDatetimeString();
1809
1811
  updateElement[procedureDef.bitacora.targetTableBitacoraFields.end_status] = status;
1810
1812
  updateElement[procedureDef.bitacora.targetTableBitacoraFields.has_error] = hasError;
1811
1813
  }else{
1812
1814
  //empezó ejecucion
1813
- updateElement[procedureDef.bitacora.targetTableBitacoraFields.init_date] = initDatetimeString;
1815
+ updateElement[procedureDef.bitacora.targetTableBitacoraFields.init_date] = initDatetimeString;
1814
1816
  }
1815
1817
  await be.inTransaction(req,async function(client){
1816
1818
  targetTableUpdateFieldsCondition.forEach(function(field){
@@ -2114,7 +2116,7 @@ AppBackend.prototype.optsGenericForFiles = function optsGenericForFiles(req, opt
2114
2116
  return changing(be.optsGenericForAll||{},{
2115
2117
  allowedExts:be.exts.normal,
2116
2118
  jade:{
2117
- skin:skin,
2119
+ skin:skin,
2118
2120
  skinUrl:skinUrl,
2119
2121
  formTitle:title,
2120
2122
  ...(opts?.withFlash ? {flash:req?.flash?.()} : {}),
@@ -2133,8 +2135,8 @@ AppBackend.prototype.unloggedLandPage = function unloggedLandPage(req){
2133
2135
  html.h2({style:'text-align:center; margin-top:15%; font-family:arial, sans-serif'},[be.messages.server.notLoggedIn]),
2134
2136
  html.h2({style:'text-align:center'},[html.code([
2135
2137
  html.a({
2136
- id:'goto-login',
2137
- style:'border:0.5px solid blue; border-radius: 6px; padding: 6px',
2138
+ id:'goto-login',
2139
+ style:'border:0.5px solid blue; border-radius: 6px; padding: 6px',
2138
2140
  href:Path.posix.join(be.config.server["base-url"],(be.config.login.plus.loginUrlPath||'/login'))
2139
2141
  },'login')
2140
2142
  ])]),
@@ -2157,8 +2159,8 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
2157
2159
  var now = bestGlobals.datetime.now();
2158
2160
  var token = crypto.randomUUID();
2159
2161
  var {rows} = await client.query(`select ${be.db.quoteIdent(be.config.login.userFieldName)} as username
2160
- from ${be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':''}${be.db.quoteIdent(be.config.login.table)}
2161
- where $1 in (${be.db.quoteIdentList(be.config.login.forget.mailFields)})
2162
+ from ${be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':''}${be.db.quoteIdent(be.config.login.table)}
2163
+ where $1 in (${be.db.quoteIdentList(be.config.login.forget.mailFields)})
2162
2164
  and ${be.config.login.activeClausule}
2163
2165
  and ${be.config.login.lockedClausule} is not true
2164
2166
  `, [req.body.email.toLowerCase()]).fetchAll();
@@ -2266,21 +2268,21 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
2266
2268
  // http://localhost:3033/img/login-logo-icon.png
2267
2269
  mainApp.get(Path.posix.join(baseUrl,'/img/login-logo-icon.png'), async function(req,res,next){
2268
2270
  var buscar = [
2269
- 'unlogged/img/login-logo-icon.svg',
2270
- 'dist/unlogged/img/login-logo-icon.svg',
2271
- 'dist/client/unlogged/img/login-logo-icon.svg',
2272
- 'unlogged/img/login-logo-icon.png',
2273
- 'dist/unlogged/img/login-logo-icon.png',
2274
- 'dist/client/unlogged/img/login-logo-icon.png',
2275
- 'unlogged/img/logo.png',
2276
- 'dist/unlogged/img/logo.png',
2277
- 'dist/client/unlogged/img/logo.png',
2278
- 'client/img/logo.png',
2279
- 'dist/client/img/logo.png',
2280
- 'dist/client/client/img/logo.png',
2271
+ 'unlogged/img/login-logo-icon.svg',
2272
+ 'dist/unlogged/img/login-logo-icon.svg',
2273
+ 'dist/client/unlogged/img/login-logo-icon.svg',
2274
+ 'unlogged/img/login-logo-icon.png',
2275
+ 'dist/unlogged/img/login-logo-icon.png',
2276
+ 'dist/client/unlogged/img/login-logo-icon.png',
2277
+ 'unlogged/img/logo.png',
2278
+ 'dist/unlogged/img/logo.png',
2279
+ 'dist/client/unlogged/img/logo.png',
2280
+ 'client/img/logo.png',
2281
+ 'dist/client/img/logo.png',
2282
+ 'dist/client/client/img/logo.png',
2281
2283
  'unlogged/img/logo-128.png',
2282
2284
  'dist/unlogged/img/logo-128.png',
2283
- 'dist/client/unlogged/img/logo-128.png'
2285
+ 'dist/client/unlogged/img/logo-128.png'
2284
2286
  ];
2285
2287
  buscar = buscar.map(n=>be.rootPath+'/'+n);
2286
2288
  buscar.push(__dirname+'/../for-client/img/login-logo-icon.png');
@@ -2295,7 +2297,7 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
2295
2297
  be.clientIncludesCompleted(null).filter(x => x.module).forEach(function (moduleDef) {
2296
2298
  if(baseUrl=='/'){
2297
2299
  baseUrl='';
2298
- }
2300
+ }
2299
2301
  let baseLib = baseUrl + '/' + (moduleDef.path ? moduleDef.path : be.esJavascript(moduleDef.type)? 'lib': 'css');
2300
2302
  resolve_module_dir(moduleDef.module, moduleDef.modPath, moduleDef.file ?? '.')
2301
2303
  try {
@@ -2319,7 +2321,7 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
2319
2321
  // ----------------------------------------------------
2320
2322
  var skin=be.config['client-setup'].skin;
2321
2323
  var skinUrl=(skin?skin+'/':'');
2322
- var optsGenericForFilesUnlogged=be.optsGenericForFiles();
2324
+ var optsGenericForFilesUnlogged=be.optsGenericForFiles();
2323
2325
  var skinPaths=[Path.join(be.rootPath,'skins')];
2324
2326
  if(be.config.server.skins[skin]['local-path']){
2325
2327
  skinPaths=skinPaths.concat(be.config.server.skins[skin]['local-path']).map(function(path){
@@ -2372,12 +2374,14 @@ AppBackend.prototype.getVisibleMenu = function getVisibleMenu(menu, context){
2372
2374
 
2373
2375
  AppBackend.prototype.clientIncludes = function clientIncludes(req, opts) {
2374
2376
  const hideBEPlusInclusions = opts === true || opts && typeof opts == "object" && opts.hideBEPlusInclusions;
2375
- opts = opts === true ? {} : opts || {};
2377
+ opts = opts === true ? {} : opts || {};
2376
2378
  var list = [];
2377
2379
  if (!hideBEPlusInclusions) {
2378
2380
  list = [
2379
2381
  // { type: 'js', module: 'xlsx', modPath: 'dist', file: 'xlsx.core.min.js' },
2380
- { type: 'js', module: 'xlsx', modPath: 'dist', file: 'xlsx.full.min.js' },
2382
+ { type: 'js', module: 'fflate', modPath: '../umd', file: 'index.js' },
2383
+ { type: 'js', module: 'xlsx-now', modPath: '../../umd', file: 'xlsx-now.umd.js' },
2384
+ { type: 'js', module: 'xlsx-now', modPath: '../../umd', file: 'xlsx-now-browser.umd.js' },
2381
2385
  { type: 'js', module: 'require-bro' },
2382
2386
  { type: 'js', module: 'js-yaml', modPath: 'browser', file: 'js-yaml.umd.min.js' },
2383
2387
  { type: 'js', module: 'cast-error' },
@@ -2463,7 +2467,7 @@ AppBackend.prototype.clientModules = function clientModules(req, opts) {
2463
2467
  }
2464
2468
 
2465
2469
  /**
2466
- * @param {string} tableName
2470
+ * @param {string} tableName
2467
2471
  * @param {(tableDef:typesOpe.TableDefinition, context?:TableContext)=>void} appenderFunction
2468
2472
  */
2469
2473
  AppBackend.prototype.appendToTableDefinition = function appendToTableDefinition(tableName, appenderFunction){
@@ -2520,16 +2524,16 @@ AppBackend.prototype.csss = function csss(hideBEPlusInclusions){
2520
2524
  AppBackend.prototype.scanSkinFiles = function scanSkinFiles() {
2521
2525
  var be = this;
2522
2526
  var skinName = be.config['client-setup'].skin;
2523
- be.activeSkinFiles = new Set();
2527
+ be.activeSkinFiles = new Set();
2524
2528
 
2525
2529
  var skinConfig = be.config.server.skins && be.config.server.skins[skinName];
2526
2530
 
2527
2531
  if (skinName && skinConfig && skinConfig['local-path']) {
2528
2532
  try {
2529
2533
  var skinPath = Path.join(Path.resolve(skinConfig['local-path']), skinName);
2530
-
2534
+
2531
2535
  if (fs.existsSync(skinPath)) {
2532
-
2536
+
2533
2537
  const walk = (currentPath) => {
2534
2538
  const entries = fs.readdirSync(currentPath, { withFileTypes: true });
2535
2539
 
@@ -2544,7 +2548,7 @@ AppBackend.prototype.scanSkinFiles = function scanSkinFiles() {
2544
2548
  var relativePath = Path.relative(skinPath, fullPath)
2545
2549
  .replace(/\\/g, '/')
2546
2550
  .replace(/^\//, '');
2547
-
2551
+
2548
2552
  be.activeSkinFiles.add(relativePath);
2549
2553
  }
2550
2554
  }
@@ -2631,7 +2635,7 @@ AppBackend.prototype.mainPage = function mainPage(req, offlineMode, opts){
2631
2635
 
2632
2636
  var lastDotIndex = css.lastIndexOf('.');
2633
2637
  var cssBase = (lastDotIndex !== -1) ? css.substring(0, lastDotIndex) : css;
2634
-
2638
+
2635
2639
  var existsInSkin = EXTENSIONES_SKIN.some(function(ext) {
2636
2640
  return be.activeSkinFiles.has(cssBase + ext);
2637
2641
  });
@@ -2897,7 +2901,7 @@ AppBackend.prototype.validateBitacora = function validateBitacora(procedureDef){
2897
2901
  var tableDefFields = be.tableStructures[procedureDef.bitacora.targetTable](contextForDump).fields;
2898
2902
  var targetTableBitacoraFields = procedureDef.bitacora.targetTableBitacoraFields;
2899
2903
  for (var fieldForSearch in targetTableBitacoraFields) {
2900
- var searchResult = tableDefFields.find(function findByName(field) {
2904
+ var searchResult = tableDefFields.find(function findByName(field) {
2901
2905
  return field.name === targetTableBitacoraFields[fieldForSearch];
2902
2906
  });
2903
2907
  if(!searchResult){
@@ -2905,12 +2909,12 @@ AppBackend.prototype.validateBitacora = function validateBitacora(procedureDef){
2905
2909
  }
2906
2910
  }
2907
2911
  var targetTableUpdateFieldsCondition = procedureDef.bitacora.targetTableUpdateFieldsCondition || ['init_date','end_date','has_error', 'end_status'];
2908
- if(targetTableUpdateFieldsCondition){
2912
+ if(targetTableUpdateFieldsCondition){
2909
2913
  if(targetTableUpdateFieldsCondition.length == 0){
2910
2914
  throw Error("Bitacora bad definition in core function '" + procedureDef.action + "', targetTableUpdateFieldsCondition must to be defined for table '" + procedureDef.bitacora.targetTable + "'.");
2911
2915
  }
2912
2916
  targetTableUpdateFieldsCondition.forEach(function(fieldName){
2913
- var searchResult = tableDefFields.find(function findByName(field) {
2917
+ var searchResult = tableDefFields.find(function findByName(field) {
2914
2918
  return field.name === fieldName;
2915
2919
  });
2916
2920
  if(!searchResult){
@@ -3036,11 +3040,13 @@ AppBackend.prototype.dumpDbTableFields = function dumpDbTableFields(tableDef, op
3036
3040
  }
3037
3041
  fields.push(
3038
3042
  ' '+db.quoteIdent(fieldDef.name)+
3039
- ' '+(fieldDef.dataLength?(fieldType=='text'?'varchar':fieldType)+'('+fieldDef.dataLength+')':fieldType)+
3043
+ ' '+(fieldDef.dataLength?(fieldType=='text'?'varchar':fieldType)+'('+fieldDef.dataLength+
3044
+ (fieldDef.dataDecimals?','+fieldDef.dataDecimals:'')
3045
+ +')':fieldType)+
3040
3046
  ( be.specialSqlDefaultExpressions[fieldDef.defaultDbValue] != null ? ' default ' + be.specialSqlDefaultExpressions[fieldDef.defaultDbValue]
3041
- : fieldDef.defaultDbValue != null ? ' default ' + fieldDef.defaultDbValue
3047
+ : fieldDef.defaultDbValue != null ? ' default ' + fieldDef.defaultDbValue
3042
3048
  : be.specialSqlDefaultExpressions[fieldDef.specialDefaultValue] != null ? ' default ' + be.specialSqlDefaultExpressions[fieldDef.specialDefaultValue]
3043
- : fieldDef.defaultValue != null ? ' default ' + db.quoteLiteral(fieldDef.defaultValue)
3049
+ : fieldDef.defaultValue != null ? ' default ' + db.quoteLiteral(fieldDef.defaultValue)
3044
3050
  : ''
3045
3051
  ) +
3046
3052
  (be.isGeneratedSequence(fieldDef.sequence)?' generated always as identity':'')+
@@ -3193,7 +3199,7 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
3193
3199
  lines.push(');');
3194
3200
  //TODO: REFACTOR: Hacerlo mas sencillo
3195
3201
  // este codigo se encarga de convertir a rights de sql nuestros propios rights
3196
- // por ej (import -> [insert, update])
3202
+ // por ej (import -> [insert, update])
3197
3203
  var allows = tableDef.allow;
3198
3204
  var appToSqlRights = {'import': ['insert', 'update'], 'export': ['select'], 'deleteAll': ['delete']};
3199
3205
  [ 'import', 'export', 'deleteAll'].filter(function(right){
@@ -3250,7 +3256,7 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
3250
3256
  var prefix = 'alter table '+cualQuoteTableName+' add '+
3251
3257
  (cons.consName?'constraint '+db.quoteIdent(cons.consName)+' ':'');
3252
3258
  switch(cons.constraintType){
3253
- case 'unique':
3259
+ case 'unique':
3254
3260
  sql='('+cons.fields.map(function(field){ return db.quoteIdent(field); }).join(', ')+')';
3255
3261
  if(cons.where){
3256
3262
  if(cons.consName == null){
@@ -3262,10 +3268,10 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
3262
3268
  prefix += 'unique ';
3263
3269
  }
3264
3270
  break;
3265
- case 'check':
3271
+ case 'check':
3266
3272
  sql='check ('+cons.expr+')';
3267
3273
  break;
3268
- case 'exclude':
3274
+ case 'exclude':
3269
3275
  sql=`exclude using ${cons.using} (${cons.fields.map(f => typeof f == "string"?`${f} WITH =`:`${f.fieldName} WITH ${f.operator}`).join(', ')})${cons.where ? ` WHERE (${cons.where})`:``}`;
3270
3276
  break;
3271
3277
  default:
@@ -3308,7 +3314,7 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
3308
3314
  (polcom.using? ` USING ( ${polcom.using} )`:'')+
3309
3315
  (polcom.check?` WITH CHECK ( ${polcom.check} )`:'')+';'
3310
3316
  );
3311
- }
3317
+ }
3312
3318
  });
3313
3319
  }
3314
3320
  }else{
@@ -3349,7 +3355,7 @@ begin
3349
3355
  else
3350
3356
  select ${be.config.login.infoFieldList.map(fieldName => db.quoteIdent(fieldName)).join(', ')}
3351
3357
  into ${be.config.login.infoFieldList.map(fieldName => db.quoteIdent('v_'+fieldName)).join(', ')}
3352
-
3358
+
3353
3359
  from ${(be.config.login.from ?? (
3354
3360
  (be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':'')+
3355
3361
  be.db.quoteIdent(be.config.login.table)))}
@@ -3360,7 +3366,7 @@ begin
3360
3366
  set backend_plus._mode = normal;
3361
3367
  end if;
3362
3368
  perform set_config('backend_plus._user', p_username, false);
3363
- end;
3369
+ end;
3364
3370
  $body$;
3365
3371
 
3366
3372
  `)
@@ -3374,7 +3380,7 @@ $body$;
3374
3380
  var allTableContent = await fs.readFile('install/local-dump.psql','utf-8');
3375
3381
  var startIndex = allTableContent.indexOf('-- Data for Name: ');
3376
3382
  console.log('startIndex', startIndex);
3377
- var lastUseful = allTableContent.lastIndexOf('\nSELECT pg_catalog.setval')
3383
+ var lastUseful = allTableContent.lastIndexOf('\nSELECT pg_catalog.setval')
3378
3384
  console.log('lastUseful', lastUseful);
3379
3385
  if (lastUseful == -1) lastUseful = allTableContent.lastIndexOf('\n\\.\n');
3380
3386
  console.log('lastUseful', lastUseful);
@@ -3454,6 +3460,7 @@ $body$;
3454
3460
  });
3455
3461
  rows=lines;
3456
3462
  }else{
3463
+ /* PARSEO DEL ARCHIVO .TAB */
3457
3464
  var lines=content.split(/\r?\n/)
3458
3465
  .filter(line => !(/^[-| ]*$/.test(line)) )
3459
3466
  .map(line => splitRawRowIntoRow(line))
@@ -3487,14 +3494,61 @@ $body$;
3487
3494
  throw Error("no se encuentra la columna "+filteredFieldDef[i]+" en "+tableName);
3488
3495
  }
3489
3496
  return value==='' ? (
3490
- def.allowEmptyText && ('nullable' in def) && !def.nullable ? "''" : 'null'
3497
+ def.allowEmptyText && ('nullable' in def) && !def.nullable ? "''" : 'null'
3491
3498
  ) : db.quoteNullable(value);
3492
3499
  }).join(', ')+")";
3493
3500
  }).join(',\n')+';\n';
3494
3501
  }
3495
3502
  dataText.push(dataString);
3503
+ if(process.env.TRY_TAB_PLUS){
3504
+ var tabPlusEmptySymbol = Symbol('empty');
3505
+ var parsedTabPlus = tabPlus.parseTab(content, {emptyField: tabPlusEmptySymbol});
3506
+ var tabPlusFields = parsedTabPlus.fields;
3507
+ var tabPlusRows = parsedTabPlus.rows;
3508
+ /* a partir de acá: solo construcción de SQL a partir de {fields,rows}, sin volver a tocar el parseo */
3509
+ var filteredFieldDefTabPlus = tabPlusFields.filter(filterField);
3510
+ var dataStringTabPlus;
3511
+ if(tablesWithStrictSequence[tableName]){
3512
+ dataStringTabPlus="COPY "+db.quoteIdent(tableName)+" ("+
3513
+ filteredFieldDefTabPlus.map(db.quoteIdent).join(', ')+
3514
+ ') FROM stdin;\n'+
3515
+ tabPlusRows.map(function(line){
3516
+ return line.filter(function(_,i){ return filterField(tabPlusFields[i]);}).map(function(value,i){
3517
+ var def = tableDef.field[filteredFieldDefTabPlus[i]];
3518
+ return value===tabPlusEmptySymbol ? (
3519
+ def.allowEmptyText && ('nullable' in def) && !def.nullable ? '' : '\\N'
3520
+ ): value.replace(/\\/g,'\\\\').replace(/\t/g,'\\t').replace(/\n/g,'\\n').replace(/\r/g,'\\r');
3521
+ }).join('\t')+'\n';
3522
+ }).join('')+'\\.\n';
3523
+ }else{
3524
+ dataStringTabPlus="insert into "+db.quoteIdent(tableName)+" ("+
3525
+ filteredFieldDefTabPlus.map(db.quoteIdent).join(', ')+
3526
+ ') values\n'+
3527
+ tabPlusRows.map(function(line){
3528
+ return "("+line.filter(function(_,i){ return filterField(tabPlusFields[i]);}).map(function(value,i){
3529
+ var def = tableDef.field[filteredFieldDefTabPlus[i]];
3530
+ if(def == null) {
3531
+ throw Error("no se encuentra la columna "+filteredFieldDefTabPlus[i]+" en "+tableName);
3532
+ }
3533
+ return value===tabPlusEmptySymbol ? (
3534
+ def.allowEmptyText && ('nullable' in def) && !def.nullable ? "''" : 'null'
3535
+ ) : db.quoteNullable(value);
3536
+ }).join(', ')+")";
3537
+ }).join(',\n')+';\n';
3538
+ }
3539
+ if(dataStringTabPlus !== dataString){
3540
+ var oldSqlPath = path.replace(/\.tab$/,'')+'-old-local.sql';
3541
+ var newSqlPath = path.replace(/\.tab$/,'')+'-new-local.sql';
3542
+ fs.writeFileSync(oldSqlPath, dataString, {encoding:'UTF8'});
3543
+ fs.writeFileSync(newSqlPath, dataStringTabPlus, {encoding:'UTF8'});
3544
+ if (process.env.TRY_TAB_PLUS == 'ON-ERROR-STOP') {
3545
+ throw new Error('TRY_TAB_PLUS: el parseo con tab-plus difiere del parseo anterior para '+tableName+' ('+path+'). Ver '+oldSqlPath+' y '+newSqlPath);
3546
+ } else {
3547
+ console.log('TRY_TAB_PLUS: el parseo con tab-plus difiere del parseo anterior para '+tableName+' ('+path+'). Ver '+oldSqlPath+' y '+newSqlPath);
3548
+ }
3549
+ }
3550
+ }
3496
3551
  }
3497
- // tablesWithStrictSequence[tableName]
3498
3552
  }
3499
3553
  }).catch(function(err){
3500
3554
  if(err.code=='ENOENT'){
@@ -3522,7 +3576,7 @@ $body$;
3522
3576
  ]
3523
3577
  .map(async function(fileNames){
3524
3578
  if (!fileNames) return '';
3525
- var i = 0;
3579
+ var i = 0;
3526
3580
  return (await Promise.all(fileNames.map(async fileName => {
3527
3581
  var content;
3528
3582
  do {
@@ -3574,7 +3628,7 @@ $body$;
3574
3628
  '\n-- functions\n' + functionLines.join('\n')+
3575
3629
  '\n-- lines \n' + lines.join('\n')+
3576
3630
  (complete? ('\n\n-- pre-ADAPTs\n'+texts[1]+'\n\n') : '' )+
3577
- (complete? ('\n\n-- DATA\n'+ dataText.join('\n')) : '' )+
3631
+ (complete? ('\n\n-- DATA\n'+ dataText.join('\n')) : '' )+
3578
3632
  (complete? ('\n\n-- ADAPTs\n'+ texts[2]+'\n\n') : '' )+
3579
3633
  '\n-- conss\n' + consLines.join('\n')+
3580
3634
  '\n-- FKs\n' + fkLines.join('\n')+
@@ -3604,14 +3658,17 @@ AppBackend.prototype.getDbFunctions = async function (){
3604
3658
  AppBackend.prototype.dumpDbSchema = async function dumpDbSchema(opts){
3605
3659
  var be = this;
3606
3660
  var {mainSql,enancePart} = await be.dumpDbSchemaPartial(
3607
- opts.complete?be.tableStructures:likeAr(be.tableStructures).filter((_, name)=>opts.tableNames.includes(name)),
3661
+ opts.complete?be.tableStructures:likeAr(be.tableStructures).filter((_, name)=>opts.tableNames.includes(name)),
3608
3662
  opts
3609
3663
  )
3610
3664
  mainSql=be.config.install.dump.db.extensions.map(function(extension){
3611
3665
  return ({
3612
- gist: "create extension if not exists btree_gist;",
3613
- pg_trgm: "create extension if not exists pg_trgm;",
3614
- pgcrypto: "create extension if not exists pgcrypto;"
3666
+ cube : "create extension if not exists cube ;",
3667
+ earthdistance : "create extension if not exists earthdistance ;",
3668
+ gist : "create extension if not exists btree_gist ;",
3669
+ pg_trgm : "create extension if not exists pg_trgm ;",
3670
+ pgcrypto : "create extension if not exists pgcrypto ;",
3671
+ postgis : "create extension if not exists postgis ;",
3615
3672
  }[extension]||('--unknown exension '+extension))+'\n';
3616
3673
  }).join('')+mainSql;
3617
3674
  await fs.writeFile('local-db-dump.sql', mainSql);
@@ -3684,7 +3741,7 @@ AppBackend.prototype.transformInput = function transformInput(fieldDef, value){
3684
3741
  return value;
3685
3742
  }
3686
3743
 
3687
- /**
3744
+ /**
3688
3745
  * xxxparam {{ action:string, parameters:any, conRegistro:boolean, conPadron:boolean, fileName?:string, csvFileName?:string, csvSeparator?:string, queries:{titulo:string, sql:string, params:string[]}[] }}
3689
3746
  * @param {{title:string, rows:Record<string, any>[]}[]} result
3690
3747
  * @returns {Promise<void>}
@@ -3782,7 +3839,7 @@ AppBackend.prototype.exportacionesGenerico = async function exportacionesGeneric
3782
3839
  ))
3783
3840
  }
3784
3841
  return [
3785
- ...(fileName?[{url:fileName, label:csvFileName?'xlsx de control':fileName}]:[]),
3842
+ ...(fileName?[{url:fileName, label:csvFileName?'xlsx de control':fileName}]:[]),
3786
3843
  ...(csvFileName?[{url:csvFileName, label:fileName?'csv (formato UTF-8)':csvFileName}]:[]),
3787
3844
  ];
3788
3845
  }