backend-plus 2.0.0-rc.2 → 2.0.0-rc.21

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.
@@ -54,6 +54,7 @@ var bestGlobals = require('best-globals');
54
54
  var coalesce = bestGlobals.coalesce;
55
55
  var changing = bestGlobals.changing;
56
56
  var datetime = bestGlobals.datetime;
57
+ var splitRawRowIntoRow = bestGlobals.splitRawRowIntoRow;
57
58
  const escapeRegExp = bestGlobals.escapeRegExp;
58
59
  var isLowerIdent = bestGlobals.isLowerIdent;
59
60
 
@@ -96,7 +97,13 @@ class AppBackend{
96
97
  /** @type {{path:string}[]} */
97
98
  this.appStack=[];
98
99
  /** @type {{message:string; fun:()=>void}[]} */
99
- this.shootDownCallbackList=[];
100
+ this.shutdownCallbackList=[];
101
+ this.sessionStores={
102
+ file: SessionFileStore,
103
+ memory: memorystore,
104
+ memoryDevel: BindMemoryPerodicallySaved(this),
105
+ "memory-saved": BindMemoryPerodicallySaved(this),
106
+ }
100
107
  if(!this.rootPath){
101
108
  console.log('ATENCIÓN hay que poner be.rootPath antes de llamar a super()');
102
109
  this.rootPath=Path.resolve(__dirname,'..');
@@ -118,11 +125,14 @@ class AppBackend{
118
125
  this.tableStructures = {};
119
126
  this.configStaticConfig();
120
127
  /** @type {null|()=>Promise<void>} */
121
- this.shootDownBackend = null;
128
+ this.shutdownBackend = null;
122
129
  /** @type {bp.Server} */
123
130
  // @ts-ignore
124
131
  this.server = null;
125
132
  }
133
+ esJavascript(type){
134
+ return ['js','mjs'].includes(type)
135
+ }
126
136
  clearCaches(){
127
137
  this.caches={
128
138
  procedures:{}
@@ -357,6 +367,8 @@ AppBackend.prototype.i18n.messages.es={
357
367
  }
358
368
  };
359
369
 
370
+ function BindMemoryPerodicallySaved(be){
371
+ return (
360
372
  /**
361
373
  * @param {Express.Session} session
362
374
  */
@@ -405,7 +417,7 @@ function MemoryPerodicallySaved(session){
405
417
  console.log('dumping sessions every',dumpEverySeconds,'seconds');
406
418
  var errorsToShow=4;
407
419
  // TODO: hangs the server in devel mode
408
- setInterval(function(){
420
+ var interval = setInterval(function(){
409
421
  try{
410
422
  fs.writeFile(fileName,json4all.stringify(store.store.dump()));
411
423
  }catch(err){
@@ -416,18 +428,15 @@ function MemoryPerodicallySaved(session){
416
428
  }
417
429
  }
418
430
  },dumpEverySeconds*1000);
431
+ be.shutdownCallbackListAdd({
432
+ message:'session saver',
433
+ fun:()=>clearInterval(interval)
434
+ })
419
435
  });
420
436
  }
421
437
  }
422
438
  return MemoryDevelConstructor;
423
- }
424
-
425
- var sessionStores={
426
- file: SessionFileStore,
427
- memory: memorystore,
428
- memoryDevel: MemoryPerodicallySaved,
429
- "memory-saved": MemoryPerodicallySaved,
430
- }
439
+ })}
431
440
 
432
441
  /**
433
442
  * @param {string} text
@@ -455,8 +464,14 @@ AppBackend.prototype.setStaticConfig = function setStaticConfig(defConfigYamlStr
455
464
  }
456
465
 
457
466
  AppBackend.prototype.configList = function configList(){
458
- var list=[this.staticConfig];
459
- if(!this.staticConfig["client-setup"].title && fs.existsSync(this.rootPath+'/def-config.yaml')){
467
+ var list=[
468
+ {
469
+ package:{
470
+ version: packagejson.version
471
+ }
472
+ },
473
+ this.staticConfig];
474
+ if(!this.staticConfig["client-setup"]?.title && fs.existsSync(this.rootPath+'/def-config.yaml')){
460
475
  console.log('DEPRECATED!!!!!!')
461
476
  console.error('ERROR el def-config hay que ponerlo dentro de staticConfig');
462
477
  console.log('DEPRECATED!!!!!!')
@@ -466,7 +481,7 @@ AppBackend.prototype.configList = function configList(){
466
481
  list.push(this.rootPath+'/def-config.yaml')
467
482
  };
468
483
  list.push(this.rootPath+'/local-config');
469
- list.push(process.env.BACKEND_PLUS_LOCAL_CONFIG||{});
484
+ list.push(process.env.BACKEND_PLUS_LOCAL_CONFIG?.trim()||{});
470
485
  return list;
471
486
  };
472
487
 
@@ -503,6 +518,11 @@ AppBackend.prototype.canChangePass = async function canChangePass(reqOrContext,
503
518
  return be.isAdmin(reqOrContext);
504
519
  }
505
520
 
521
+ AppBackend.prototype.shutdownCallbackListAdd = function(messageFun){
522
+ this.shutdownCallbackList.push(messageFun);
523
+ }
524
+
525
+
506
526
  AppBackend.prototype._Browsers = {
507
527
  Edge: {short:'Ed' , minVer:14 , polly:true},
508
528
  Konqueror: {short:'Kq' , minVer:null, polly:true},
@@ -519,7 +539,7 @@ var imgExts = ['jpg', 'png', 'jpeg', 'ico', 'gif', 'svg']
519
539
 
520
540
  AppBackend.prototype.exts = {
521
541
  img: imgExts,
522
- normal: ['', 'js', 'map', 'html', 'css', ...imgExts, 'appcache', 'manifest', 'json', 'webmanifest', 'zip', 'pdf', ...fontExts, 'xlsx', 'csv']
542
+ normal: ['', 'js', 'map', 'html', 'css', ...imgExts, 'appcache', 'manifest', 'json', 'webmanifest', 'zip', 'pdf', ...fontExts, 'xlsx', 'csv', 'mjs']
523
543
  };
524
544
 
525
545
  /**
@@ -625,32 +645,33 @@ AppBackend.prototype.start = function start(opts){
625
645
  // @ts-ignore : only for testing */
626
646
  this.getMainApp = function getMainApp(){ return mainApp; };
627
647
  }
628
- this.shootDownBackend = function shootDownBackend(){
648
+ this.shutdownBackend = async function shutdownBackend(){
629
649
  console.log('shooting down:');
630
- return new Promise(function(resolve,reject){
631
- console.log('*','express server');
632
- be.server.close(/** @param {Error} err */function(err){
633
- if(err){
634
- console.log('*', err)
635
- reject(err);
636
- }else{
637
- resolve();
638
- }
639
- });
640
- }).then(async function(){
641
- while(be.shootDownCallbackList.length){
642
- var nextCallback=be.shootDownCallbackList.pop();
643
- if(nextCallback!=null){
644
- console.log('*',nextCallback.message);
645
- await nextCallback.fun();
646
- }
647
- }
648
- }).then(function(){
649
- console.log('pg pool balance',pg.poolBalanceControl());
650
- // @ts-ignore al hacer shootDown ya no hay mainApp.
651
- mainApp = null;
652
- logWhy && logWhy();
653
- });
650
+ var waitFor = [
651
+ new Promise(function(resolve,reject){
652
+ console.log('*','express server');
653
+ be.server.close(/** @param {Error} err */function(err){
654
+ if(err){
655
+ console.log('*', err)
656
+ reject(err);
657
+ }else{
658
+ console.log('*','express server done!')
659
+ resolve();
660
+ }
661
+ });
662
+ }),
663
+ ...(be.shutdownCallbackList.map(x => (async function(){
664
+ console.log('shut:', x.message)
665
+ await x.fun()
666
+ console.log('done:', x.message)
667
+ })()))
668
+ ];
669
+ console.log('*', 'waiting for all')
670
+ await Promise.all(waitFor);
671
+ console.log('*', 'all done')
672
+ mainApp = null;
673
+ console.log('* logWhy',logWhy)
674
+ logWhy && logWhy();
654
675
  };
655
676
  return Promise.resolve().then(function(){
656
677
  var configList=be.configList();
@@ -681,10 +702,10 @@ AppBackend.prototype.start = function start(opts){
681
702
  }
682
703
  var sessionStoreName=be.config.server["session-store"];
683
704
  if(sessionStoreName){
684
- if(config.devel && sessionStores[sessionStoreName+'Devel']){
705
+ if(config.devel && be.sessionStores[sessionStoreName+'Devel']){
685
706
  sessionStoreName+='Devel';
686
707
  }
687
- var storeModule = sessionStores[sessionStoreName];
708
+ var storeModule = be.sessionStores[sessionStoreName];
688
709
  be.config.login.plus.store.module = storeModule;
689
710
  }
690
711
  be.config.install.dump.db.owner=coalesce(be.config.db.owner,be.config.install.dump.db.owner,be.config.db.user);
@@ -704,7 +725,7 @@ AppBackend.prototype.start = function start(opts){
704
725
  throw new Error("backend-plus: Motor not recongnized: "+be.config.db.motor);
705
726
  }
706
727
  be.db = pg;
707
- be.dbUserNameExpr="split_part(current_setting('application_name'),' ',1)";
728
+ be.dbUserNameExpr="get_app_user()";
708
729
  be.dbUserRolExpr=`(select ${be.db.quoteIdent(be.config.login.rolFieldName)}
709
730
  from ${be.config.login.schema?be.db.quoteIdent(be.config.login.schema)+'.':''}${be.db.quoteIdent(be.config.login.table)}
710
731
  where ${be.db.quoteIdent(be.config.login.userFieldName)} = ${be.dbUserNameExpr})`
@@ -760,6 +781,7 @@ AppBackend.prototype.start = function start(opts){
760
781
  pg.log.inFileName = 'last-pg-error-local.sql'
761
782
  pg.logLastError.inFileName = 'last-pg-error-local.sql'
762
783
  }
784
+ be.config.db.search_path = be.config.db.search_path ?? [be.config.db.schema, 'public'];
763
785
  be.getDbClient = function getDbClient(req){
764
786
  var paramsDb = be.DoubleDragon?.dbParams?.[req?.user?.[be.config.login.userFieldName]] ?? be.config.db;
765
787
  return pg.connect(paramsDb).then(function(client){
@@ -771,7 +793,7 @@ AppBackend.prototype.start = function start(opts){
771
793
  return client.query(
772
794
  "SET application_name = "+be.db.quoteLiteral(dbAppName)
773
795
  ).execute().then(function(){
774
- var search_path = be.config.db.search_path || [be.config.db.schema, 'public'];
796
+ var search_path = be.config.db.search_path;
775
797
  if(search_path.length>0){
776
798
  return client.query("set SEARCH_PATH TO "+be.db.quoteIdentList(search_path)).execute().then(function(){
777
799
  return client;
@@ -961,12 +983,18 @@ AppBackend.prototype.start = function start(opts){
961
983
  loginPlusOpts.noLoggedUrlPath=be.config.login.unloggedLandPage;
962
984
  }
963
985
  mainApp.loginPlusManager.init(mainApp,changing(be.config.login.plus,loginPlusOpts));
964
- be.shootDownCallbackList.push({
986
+ be.shutdownCallbackListAdd({
965
987
  message:'login-plus manager',
966
988
  fun:function(){
967
989
  mainApp.loginPlusManager.closeManager();
968
990
  }
969
991
  });
992
+ be.shutdownCallbackListAdd({
993
+ message:'pg',
994
+ fun:function(){
995
+ pg.shutdown();
996
+ }
997
+ });
970
998
  mainApp.loginPlusManager.setValidatorStrategy(
971
999
  function(req, username, password, done) {
972
1000
  var client;
@@ -1130,7 +1158,7 @@ AppBackend.prototype.start = function start(opts){
1130
1158
  }
1131
1159
  return be.sendMail({
1132
1160
  to: be.config.mailer?.supervise?.to,
1133
- subject: `npm start ${be.config["client-setup"].title || packagejson.name} ok ✔️`,
1161
+ subject: `npm start ${be.config["client-setup"]?.title || packagejson.name} ok ✔️`,
1134
1162
  text:`Inicio del servicio: ${new Date().toJSON()}
1135
1163
 
1136
1164
  Contexto: ${os.userInfo().username} ${process.cwd()}
@@ -1152,7 +1180,7 @@ AppBackend.prototype.start = function start(opts){
1152
1180
  }
1153
1181
  var mailDeAvisoDeFalla = be.sendMail({
1154
1182
  to: be.config.mailer?.supervise?.to,
1155
- subject: `npm start ${be.config["client-setup"].title || packagejson.name} fallido 🛑`,
1183
+ subject: `npm start ${be.config["client-setup"]?.title || packagejson.name} fallido 🛑`,
1156
1184
  text:`Falla en el inicio del servicio: ${new Date().toJSON()}
1157
1185
 
1158
1186
  Contexto: ${os.userInfo().username} ${process.cwd()}
@@ -1209,13 +1237,14 @@ AppBackend.prototype.checkDatabaseStructure = async function checkDatabaseStruct
1209
1237
  }
1210
1238
  if(be.config.login?.forget){
1211
1239
  try{
1212
- await client.query(`select tokentype, info, due from tokens limit 1`).fetchOneRowIfExists();
1240
+ await client.query(`select tokentype, info, due from his.tokens limit 1`).fetchOneRowIfExists();
1213
1241
  }catch(err){
1214
1242
  var mensaje = `
1215
1243
  --------quizas falten los campos en la tabla tokens:
1216
1244
  alter table tokens add column tokentype text;
1217
1245
  alter table tokens add column info jsonb;
1218
1246
  alter table tokens add column due timestamp;
1247
+ --------quizas falte moverla al esquema his.
1219
1248
  `;
1220
1249
  err.message += mensaje;
1221
1250
  console.log(mensaje)
@@ -1253,6 +1282,21 @@ AppBackend.prototype.checkDatabaseStructure = async function checkDatabaseStruct
1253
1282
  `;
1254
1283
  throw new Error(message);
1255
1284
  }
1285
+ var {rows: sql_routines} = await client.query(`SELECT routine_name, routine_schema, routine_definition
1286
+ FROM information_schema.routines
1287
+ WHERE routine_schema in (${be.config.db.search_path.map(path => be.db.quoteLiteral(path)).join(', ')})
1288
+ `).fetchAll();
1289
+ var sqlRoutines = likeAr.toPlainObject(sql_routines, 'routine_name');
1290
+ var message = ''
1291
+ likeAr(AppBackend.prototype.sql_routines).forEach((routine_name, def) => {
1292
+ if (sqlRoutines[routine_name] && def.dump.includes(sqlRoutines[routine_name].routine_definition)) {
1293
+ message += `
1294
+ ----- hay que crear o actualizar la rutina ${routine_name}:
1295
+ ${dump}
1296
+ `;
1297
+ }
1298
+ })
1299
+ if (message) throw new Error(message);
1256
1300
  };
1257
1301
 
1258
1302
  AppBackend.prototype.postConfig = function postConfig(){
@@ -1511,17 +1555,17 @@ AppBackend.prototype.addProcedureServices = function addProcedureServices(forUnl
1511
1555
  });
1512
1556
  }else{
1513
1557
  if(progressInfo.lengthComputable){
1514
- if(new Date()-(context.lastMessageSended||0) > 500 || progressInfo.force){
1515
- context.lastMessageSended=new Date();
1558
+ if (!context.lastMessageSended) {
1559
+ context.lastMessageSended = {}
1560
+ }
1561
+ if(new Date()-(context.lastMessageSended[progressInfo.idGroup]||0) > 500 || progressInfo.force){
1562
+ context.lastMessageSended[progressInfo.idGroup]=new Date();
1516
1563
  res.write(JSON.stringify({progress:progressInfo})+"\n");
1517
1564
  if(progressInfo.total){
1518
1565
  var rate100=Math.floor(progressInfo.loaded*100/progressInfo.total);
1519
1566
  var rate1000=Math.floor(progressInfo.loaded*1000/progressInfo.total);
1520
- if(rate100<1 && progressInfo.loaded>0){
1521
- progress2send={message:'('+(rate1000||'½')+'‰)', ephemeral:true};
1522
- }else{
1523
- progress2send={message:rate100+'%', ephemeral:true};
1524
- }
1567
+ var message = rate100<1 && progressInfo.loaded>0 ? '('+(rate1000||'½')+'‰)' : rate100+'%';
1568
+ progress2send={message, ephemeral:true, idGroup:progressInfo.idGroup};
1525
1569
  }
1526
1570
  }else{
1527
1571
  progress2send=null;
@@ -1894,6 +1938,9 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
1894
1938
  // http://localhost:3033/img/login-logo-icon.png
1895
1939
  mainApp.get(Path.posix.join(baseUrl,'/img/login-logo-icon.png'), async function(req,res,next){
1896
1940
  var buscar = [
1941
+ 'unlogged/img/login-logo-icon.svg',
1942
+ 'dist/unlogged/img/login-logo-icon.svg',
1943
+ 'dist/client/unlogged/img/login-logo-icon.svg',
1897
1944
  'unlogged/img/login-logo-icon.png',
1898
1945
  'dist/unlogged/img/login-logo-icon.png',
1899
1946
  'dist/client/unlogged/img/login-logo-icon.png',
@@ -1921,7 +1968,7 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
1921
1968
  if(baseUrl=='/'){
1922
1969
  baseUrl='';
1923
1970
  }
1924
- let baseLib = baseUrl + '/' + (moduleDef.path ? moduleDef.path : moduleDef.type == 'js'? 'lib': 'css');
1971
+ let baseLib = baseUrl + '/' + (moduleDef.path ? moduleDef.path : be.esJavascript(moduleDef.type)? 'lib': 'css');
1925
1972
  try {
1926
1973
  var allowedExts=(moduleDef.type=='js'?['js','map']:[moduleDef.type]);
1927
1974
  mainApp.use(baseLib, serveContent(resolve_module_dir(moduleDef.module, moduleDef.modPath), { allowedExts }));
@@ -2056,6 +2103,7 @@ AppBackend.prototype.clientIncludes = function clientIncludes(req, opts) {
2056
2103
 
2057
2104
  AppBackend.prototype.clientIncludesCompleted = function clientIncludesCompleted(req, opts) {
2058
2105
  let list = this.clientIncludes(req, opts);
2106
+ var be = this;
2059
2107
  if(this.config.devel && this.config.devel.useFileDevelopment){
2060
2108
  list=list.map(includeDef=>{
2061
2109
  if(includeDef.fileProduction){
@@ -2071,14 +2119,15 @@ AppBackend.prototype.clientIncludesCompleted = function clientIncludesCompleted(
2071
2119
  return list.map(inclusion =>{
2072
2120
  var filename = inclusion.file? inclusion.file: (inclusion.module? Path.basename(require_resolve(inclusion.module,rPaths)): '');
2073
2121
  return changing({
2074
- src: inclusion.type == 'js' ? ((inclusion.path ? inclusion.path : 'lib') + '/' + (inclusion.file ? inclusion.file : filename)) : '',
2122
+ src: be.esJavascript(inclusion.type) ? ((inclusion.path ? inclusion.path : 'lib') + '/' + (inclusion.file ? inclusion.file : filename)) : '',
2075
2123
  href: inclusion.type == 'css' ? ((inclusion.path ? inclusion.path : 'css') + '/' + (inclusion.file ? inclusion.file : (inclusion.module + '.css'))) : ''
2076
2124
  }, inclusion);
2077
2125
  });
2078
2126
  }
2079
2127
 
2080
2128
  AppBackend.prototype.clientModules = function clientModules(req, opts) {
2081
- return { scripts: this.clientIncludesCompleted(req, opts).filter(x => x.type == 'js').map(mod => {return { src: mod.src }})};
2129
+ var be = this;
2130
+ return { scripts: this.clientIncludesCompleted(req, opts).filter(x => be.esJavascript(x.type)).map(mod => {return { src: mod.src, type:mod.type=='mjs'?"module":null }})};
2082
2131
  }
2083
2132
 
2084
2133
  /**
@@ -2181,7 +2230,7 @@ AppBackend.prototype.mainPage = function mainPage(req, offlineMode, opts){
2181
2230
  }
2182
2231
  return html.html(attr,[
2183
2232
  html.head([
2184
- html.title(be.config["client-setup"].title),
2233
+ html.title(be.config["client-setup"]?.title),
2185
2234
  html.meta({charset:"utf-8"}),
2186
2235
  viewportAttrs?html.meta(viewportAttrs):null,
2187
2236
  html.link({href: opts.icons.iconShortcut , rel: "shortcut icon", type: "image/png"}),
@@ -2804,10 +2853,11 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2804
2853
  lines.push('');
2805
2854
  if(tableDef.sql.policies.enabled){
2806
2855
  policyLines.push(`ALTER TABLE ${cualQuoteTableName} ENABLE ROW LEVEL SECURITY;`);
2807
- ['all', 'select', 'insert', 'update', 'delete'].forEach((command)=>{
2808
- var polcom=tableDef.sql.policies[command];
2809
- if(polcom.using || polcom.check){
2810
- policyLines.push(`CREATE POLICY bp_pol_${command} ON ${cualQuoteTableName} AS PERMISSIVE FOR ${command}`+
2856
+ [null, 'all', 'select', 'insert', 'update', 'delete'].forEach((command)=>{
2857
+ var polcom=tableDef.sql.policies[command] ?? {using: `true`, permissive:true};
2858
+ if(polcom?.using || polcom?.check){
2859
+ policyLines.push(`CREATE POLICY ${be.db.quoteIdent(polcom.name ?? `bp ${command ?? `base`}`)} ON ${cualQuoteTableName} `+
2860
+ `AS ${polcom.permissive ? `PERMISSIVE` : `RESTRICTIVE`} FOR ${command ?? `all`} TO ${be.config.db.user}`+
2811
2861
  (polcom.using? ` USING ( ${polcom.using} )`:'')+
2812
2862
  (polcom.check?` WITH CHECK ( ${polcom.check} )`:'')+';'
2813
2863
  );
@@ -2842,12 +2892,18 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2842
2892
  if (err.code != 'ENOENT') throw err;
2843
2893
  try {
2844
2894
  var allTableContent = await fs.readFile('install/local-dump.psql','utf-8');
2845
- var startIndex = allTableContent.search(/\n-- Data for Name: /);
2846
- var lastIndex = allTableContent.search(/\nSELECT pg_catalog.setval/);
2895
+ var startIndex = allTableContent.indexOf('-- Data for Name: ');
2896
+ console.log('startIndex', startIndex);
2897
+ var lastUseful = allTableContent.lastIndexOf('\nSELECT pg_catalog.setval')
2898
+ console.log('lastUseful', lastUseful);
2899
+ if (lastUseful == -1) lastUseful = allTableContent.lastIndexOf('\n\\.\n');
2900
+ console.log('lastUseful', lastUseful);
2901
+ var lastIndex = allTableContent.indexOf('\n--', lastUseful);
2902
+ console.log('lastIndex', lastIndex);
2847
2903
  allTableData = allTableContent.slice(startIndex, lastIndex);
2848
2904
  } catch(err) {
2849
2905
  if (err.code != 'ENOENT') throw err;
2850
- throw err;
2906
+ //throw err;
2851
2907
  }
2852
2908
  }
2853
2909
  if (allTableData) {
@@ -2920,29 +2976,39 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2920
2976
  }else{
2921
2977
  var lines=content.split(/\r?\n/)
2922
2978
  .filter(line => !(/^[-| ]*$/.test(line)) )
2923
- .map(line => line.split(/(?<!(?:^|[^\\])(?:\\\\)*\\)\|/).map(item => item.trimRight().replace(/\\(.)/g,(_,l)=>(l=='t'?'\t':l=='r'?'\r':l=='n'?'\n':l=='s'?' ':l))) )
2979
+ .map(line => splitRawRowIntoRow(line))
2924
2980
  .filter(line => line.length>1 || line.length==1 && line[0].trim() );
2925
2981
  if(lines.length>1){
2926
2982
  if(lines[0][0].startsWith('\ufeff')){
2927
2983
  lines[0][0]=lines[0][0].replace('\ufeff','')
2928
2984
  }
2929
2985
  rows=lines.slice(1);
2986
+ var filteredFieldDef = lines[0].filter(filterField);
2930
2987
  if(tablesWithStrictSequence[tableName]){
2931
2988
  var dataString="COPY "+db.quoteIdent(tableName)+" ("+
2932
- lines[0].filter(filterField).map(db.quoteIdent).join(', ')+
2989
+ filteredFieldDef.map(db.quoteIdent).join(', ')+
2933
2990
  ') FROM stdin;\n'+
2934
2991
  rows.map(function(line){
2935
- return line.filter(function(_,i){ return filterField(lines[0][i]);}).map(function(value){
2936
- return value===''?'\\N':value.replace(/\\/g,'\\\\').replace(/\t/g,'\\t').replace(/\n/g,'\\n').replace(/\r/g,'\\r');
2992
+ return line.filter(function(_,i){ return filterField(lines[0][i]);}).map(function(value,i){
2993
+ var def = tableDef.field[filteredFieldDef[i]];
2994
+ return value==='' ? (
2995
+ def.allowEmptyText && ('nullable' in def) && !def.nullable ? '' : '\\N'
2996
+ ): value.replace(/\\/g,'\\\\').replace(/\t/g,'\\t').replace(/\n/g,'\\n').replace(/\r/g,'\\r');
2937
2997
  }).join('\t')+'\n';
2938
2998
  }).join('')+'\\.\n';
2939
2999
  }else{
2940
3000
  var dataString="insert into "+db.quoteIdent(tableName)+" ("+
2941
- lines[0].filter(filterField).map(db.quoteIdent).join(', ')+
3001
+ filteredFieldDef.map(db.quoteIdent).join(', ')+
2942
3002
  ') values\n'+
2943
3003
  rows.map(function(line){
2944
- return "("+line.filter(function(_,i){ return filterField(lines[0][i]);}).map(function(value){
2945
- return value===''?'null':db.quoteNullable(value);
3004
+ return "("+line.filter(function(_,i){ return filterField(lines[0][i]);}).map(function(value,i){
3005
+ var def = tableDef.field[filteredFieldDef[i]];
3006
+ if(def == null) {
3007
+ throw Error("no se encuentra la columna "+filteredFieldDef[i]+" en "+tableName);
3008
+ }
3009
+ return value==='' ? (
3010
+ def.allowEmptyText && ('nullable' in def) && !def.nullable ? "''" : 'null'
3011
+ ) : db.quoteNullable(value);
2946
3012
  }).join(', ')+")";
2947
3013
  }).join(',\n')+';\n';
2948
3014
  }
@@ -2965,28 +3031,65 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2965
3031
  console.log('silence "skipping content" messages in "local-config.yaml".install.dump.skip-content=true');
2966
3032
  }
2967
3033
  }
3034
+ let installFolders = be.config.install.dump.folders ?? ['install']
2968
3035
  let texts = await Promise.all(
2969
- ['prepare.sql','pre-adapt.sql','adapt.sql']
2970
- .concat(be.config.install.dump.scripts['prepare'])
2971
- .concat(be.config.install.dump.scripts['post-adapt'])
2972
- .map(function(fileName){
2973
- return fs.readFile(be.rootPath+'/install/'+fileName, {encoding:'UTF8'}).catch(function(err){
2974
- if(err.code!='ENOENT'){
2975
- throw err;
2976
- }
2977
- console.log("err:",err);
2978
- return '-- no '+fileName+'\n';
2979
- }).then(function(content){
2980
- return '-- '+fileName+'\n'+content;
2981
- });
3036
+ [
3037
+ ['prepare.sql'],
3038
+ ['pre-adapt.sql'].concat(be.config.install.dump.scripts['pre-adapt']),
3039
+ ['adapt.sql'],
3040
+ be.config.install.dump.scripts['prepare'] ?? [],
3041
+ be.config.install.dump.scripts['post-adapt'] ?? []
3042
+ ]
3043
+ .map(async function(fileNames){
3044
+ if (!fileNames) return '';
3045
+ var i = 0;
3046
+ return (await Promise.all(fileNames.map(async fileName => {
3047
+ var content;
3048
+ do {
3049
+ var folder = installFolders[i];
3050
+ try{
3051
+ content = await fs.readFile(be.rootPath+'/'+folder+'/'+fileName, {encoding:'UTF8'});
3052
+ } catch (err) {
3053
+ if(err.code!='ENOENT') throw err;
3054
+ }
3055
+ i++;
3056
+ } while (i < installFolders.length && !content);
3057
+ if (!content) {
3058
+ return '-- no '+fileName+'\n';
3059
+ } else {
3060
+ return '-- '+folder+'/'+fileName+'\n'+content;
3061
+ };
3062
+ }))).join('\n')
2982
3063
  })
2983
3064
  );
3065
+
3066
+ var common = (await Promise.all(be.appStack.map(async function(stackNode){
3067
+ var common = [];
3068
+ for (var prefix of ['../', '../../']) {
3069
+ try {
3070
+ var dirName = Path.join(stackNode.path,prefix+'install').replace(regexpDistReplacer,'$1$2')
3071
+ var list = await fs.readdir(dirName);
3072
+ } catch (err) {
3073
+ if (err.code != 'ENOENT') throw err;
3074
+ var list = [];
3075
+ }
3076
+ for(var fileName of list){
3077
+ if (fileName.endsWith('-fun.sql')) {
3078
+ common.push(await fs.readFile(Path.join(dirName,fileName), 'utf-8'));
3079
+ }
3080
+ }
3081
+ }
3082
+ return common.join('\n');
3083
+ }))).join('\n');
3084
+
2984
3085
  var prepareList=(be.config.install.dump.scripts['prepare']||[]);
2985
3086
  var mainSql=(
2986
3087
  (complete? linesCreate.join('\n'): '')+
2987
3088
  (complete||opts.forDump? searchPathline.join('\n'): '')+
3089
+ '\n-- common'+
3090
+ common+'\n'+
2988
3091
  (complete? '\n\n--prepare.sql\n'+ texts[0]+'\n\n' :'' )+
2989
- (complete? texts.slice(3,3+prepareList.length).join('\n\n')+'\n\n' : '' )+
3092
+ (complete? texts[3] + '\n\n' : '' )+
2990
3093
  '\n-- functions\n' + functionLines.join('\n')+
2991
3094
  '\n-- lines \n' + lines.join('\n')+
2992
3095
  (complete? ('\n\n-- pre-ADAPTs\n'+texts[1]+'\n\n') : '' )+
@@ -2996,7 +3099,7 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2996
3099
  '\n-- FKs\n' + fkLines.join('\n')+
2997
3100
  '\n-- index\n' + indexLines.join('\n')+
2998
3101
  '\n-- policies\n' + policyLines.join('\n')+
2999
- (complete? texts.slice(3+prepareList.length).join('\n\n')+'\n\n' : '' )+
3102
+ (complete? texts[4] + '\n\n' : '' )+
3000
3103
  (complete? (be.config.install.dump.enances==='inline'?enancePart:'') :'')
3001
3104
  ).replace(/\uFEFF/g /*inner BOM replacing*/,'\n\n').replace(
3002
3105
  new RegExp(escapeRegExp(db.quoteIdent(be.config.install.dump.db["owner4special-scripts"])),'g'),
@@ -3117,6 +3220,8 @@ AppBackend.prototype.exportacionesGenerico = async function exportacionesGeneric
3117
3220
  if (typeof result[0].title !== "string" || typeof result[0].rows !== "object" && !(result[0].rows instanceof Array) ) {
3118
3221
  throw new Error ("exportacionesGenerico debe recibir {title:string, rows:Record<string, any>[]}")
3119
3222
  }
3223
+ csvFileName = result[0].csvFileName ?? csvFileName;
3224
+ fileName = result[0].fileName ?? fileName;
3120
3225
  await bestGlobals.sleep(100);
3121
3226
  context.informProgress({message:`buscando archivos recién generados`})
3122
3227
  /** @type {{url:string, label:string}[]} */