backend-plus 2.0.0-rc.3 → 2.0.0-rc.31

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;
@@ -1724,14 +1768,26 @@ function require_resolve(moduleName){
1724
1768
  return resolved;
1725
1769
  }
1726
1770
 
1727
- function resolve_module_dir(moduleName,path){
1771
+ function resolve_module_dir(moduleName,path,fileToCheck){
1728
1772
  var baseDir;
1729
1773
  if(packagejson.name==moduleName){
1730
1774
  baseDir=Path.join(process.cwd(), packagejson.main);
1731
1775
  }else{
1732
1776
  baseDir=require_resolve(moduleName);
1733
1777
  }
1734
- return Path.join(Path.dirname(baseDir),path||'');
1778
+ var resultDir = Path.join(Path.dirname(baseDir),path||'');
1779
+ if (fileToCheck) {
1780
+ fs.stat(Path.join(resultDir, fileToCheck)).then(function(status){
1781
+ if (!status) {
1782
+ console.error('Error resolve_module_dir in:',resultDir,'no:',fileToCheck);
1783
+ } else if(!status.isFile()) {
1784
+ console.error('Error resolve_module_dir in:',resultDir,',',fileToCheck,'is not a file');
1785
+ }
1786
+ },function(err){
1787
+ console.error('Error resolve_module_dir in:',resultDir,'err:',err);
1788
+ });
1789
+ }
1790
+ return resultDir;
1735
1791
  }
1736
1792
 
1737
1793
  AppBackend.prototype.optsGenericForFiles = function optsGenericForFiles(req, opts){
@@ -1894,6 +1950,9 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
1894
1950
  // http://localhost:3033/img/login-logo-icon.png
1895
1951
  mainApp.get(Path.posix.join(baseUrl,'/img/login-logo-icon.png'), async function(req,res,next){
1896
1952
  var buscar = [
1953
+ 'unlogged/img/login-logo-icon.svg',
1954
+ 'dist/unlogged/img/login-logo-icon.svg',
1955
+ 'dist/client/unlogged/img/login-logo-icon.svg',
1897
1956
  'unlogged/img/login-logo-icon.png',
1898
1957
  'dist/unlogged/img/login-logo-icon.png',
1899
1958
  'dist/client/unlogged/img/login-logo-icon.png',
@@ -1921,7 +1980,7 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
1921
1980
  if(baseUrl=='/'){
1922
1981
  baseUrl='';
1923
1982
  }
1924
- let baseLib = baseUrl + '/' + (moduleDef.path ? moduleDef.path : moduleDef.type == 'js'? 'lib': 'css');
1983
+ let baseLib = baseUrl + '/' + (moduleDef.path ? moduleDef.path : be.esJavascript(moduleDef.type)? 'lib': 'css');
1925
1984
  try {
1926
1985
  var allowedExts=(moduleDef.type=='js'?['js','map']:[moduleDef.type]);
1927
1986
  mainApp.use(baseLib, serveContent(resolve_module_dir(moduleDef.module, moduleDef.modPath), { allowedExts }));
@@ -1935,7 +1994,7 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
1935
1994
  }
1936
1995
  }
1937
1996
  } catch (error) {
1938
- console.error('ERROR: No se pudo servir el módulo ' + moduleDef.module);
1997
+ console.error('ERROR: No se pudo servir el módulo', moduleDef.module);
1939
1998
  throw error;
1940
1999
  }
1941
2000
  })
@@ -2056,6 +2115,7 @@ AppBackend.prototype.clientIncludes = function clientIncludes(req, opts) {
2056
2115
 
2057
2116
  AppBackend.prototype.clientIncludesCompleted = function clientIncludesCompleted(req, opts) {
2058
2117
  let list = this.clientIncludes(req, opts);
2118
+ var be = this;
2059
2119
  if(this.config.devel && this.config.devel.useFileDevelopment){
2060
2120
  list=list.map(includeDef=>{
2061
2121
  if(includeDef.fileProduction){
@@ -2071,14 +2131,15 @@ AppBackend.prototype.clientIncludesCompleted = function clientIncludesCompleted(
2071
2131
  return list.map(inclusion =>{
2072
2132
  var filename = inclusion.file? inclusion.file: (inclusion.module? Path.basename(require_resolve(inclusion.module,rPaths)): '');
2073
2133
  return changing({
2074
- src: inclusion.type == 'js' ? ((inclusion.path ? inclusion.path : 'lib') + '/' + (inclusion.file ? inclusion.file : filename)) : '',
2134
+ src: be.esJavascript(inclusion.type) ? ((inclusion.path ? inclusion.path : 'lib') + '/' + (inclusion.file ? inclusion.file : filename)) : '',
2075
2135
  href: inclusion.type == 'css' ? ((inclusion.path ? inclusion.path : 'css') + '/' + (inclusion.file ? inclusion.file : (inclusion.module + '.css'))) : ''
2076
2136
  }, inclusion);
2077
2137
  });
2078
2138
  }
2079
2139
 
2080
2140
  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 }})};
2141
+ var be = this;
2142
+ 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
2143
  }
2083
2144
 
2084
2145
  /**
@@ -2181,7 +2242,7 @@ AppBackend.prototype.mainPage = function mainPage(req, offlineMode, opts){
2181
2242
  }
2182
2243
  return html.html(attr,[
2183
2244
  html.head([
2184
- html.title(be.config["client-setup"].title),
2245
+ html.title(be.config["client-setup"]?.title),
2185
2246
  html.meta({charset:"utf-8"}),
2186
2247
  viewportAttrs?html.meta(viewportAttrs):null,
2187
2248
  html.link({href: opts.icons.iconShortcut , rel: "shortcut icon", type: "image/png"}),
@@ -2621,6 +2682,30 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2621
2682
  }
2622
2683
  var schema=db.quoteIdent(be.config.db.schema);
2623
2684
  linesCreate.push("set role to "+owner+";");
2685
+ linesCreate.push(`do
2686
+ $do$
2687
+ declare
2688
+ vExpectedVersion text := '${be.config.db["min-version"]}';
2689
+ vCurrentVersion text := (select setting from pg_settings where name = 'server_version');
2690
+ begin
2691
+ if jsonb('['||replace(vCurrentVersion,'.',',')||']') < jsonb('['||replace(vExpectedVersion,'.',',')||']') then
2692
+ raise exception 'BACKEND-PLUS DUMP: Old PostrgreSQL Version % expected %', vCurrentVersion, vExpectedVersion;
2693
+ end if;
2694
+ end;
2695
+ $do$;
2696
+ `);
2697
+ linesCreate.push(`do
2698
+ $do$
2699
+ declare
2700
+ vExpected text := 'UTF8';
2701
+ vCurrent text := coalesce((select setting from pg_settings where name = 'server_encoding'),'unknown');
2702
+ begin
2703
+ if vCurrent is distinct from vExpected then
2704
+ raise exception 'BACKEND-PLUS DUMP: PostrgreSQL server_encoding %, expected %', vCurrent, vExpected;
2705
+ end if;
2706
+ end;
2707
+ $do$;
2708
+ `);
2624
2709
  linesCreate.push("drop schema if exists "+schema+' cascade;');
2625
2710
  if (be.config.install.dump["drop-his"]) {
2626
2711
  linesCreate.push("drop schema if exists his cascade;");
@@ -2804,10 +2889,11 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2804
2889
  lines.push('');
2805
2890
  if(tableDef.sql.policies.enabled){
2806
2891
  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}`+
2892
+ [null, 'all', 'select', 'insert', 'update', 'delete'].forEach((command)=>{
2893
+ var polcom=tableDef.sql.policies[command] ?? {using: `true`, permissive:true};
2894
+ if(polcom?.using || polcom?.check){
2895
+ policyLines.push(`CREATE POLICY ${be.db.quoteIdent(polcom.name ?? `bp ${command ?? `base`}`)} ON ${cualQuoteTableName} `+
2896
+ `AS ${polcom.permissive ? `PERMISSIVE` : `RESTRICTIVE`} FOR ${command ?? `all`} TO ${be.config.db.user}`+
2811
2897
  (polcom.using? ` USING ( ${polcom.using} )`:'')+
2812
2898
  (polcom.check?` WITH CHECK ( ${polcom.check} )`:'')+';'
2813
2899
  );
@@ -2842,8 +2928,14 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2842
2928
  if (err.code != 'ENOENT') throw err;
2843
2929
  try {
2844
2930
  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/);
2931
+ var startIndex = allTableContent.indexOf('-- Data for Name: ');
2932
+ console.log('startIndex', startIndex);
2933
+ var lastUseful = allTableContent.lastIndexOf('\nSELECT pg_catalog.setval')
2934
+ console.log('lastUseful', lastUseful);
2935
+ if (lastUseful == -1) lastUseful = allTableContent.lastIndexOf('\n\\.\n');
2936
+ console.log('lastUseful', lastUseful);
2937
+ var lastIndex = allTableContent.indexOf('\n--', lastUseful);
2938
+ console.log('lastIndex', lastIndex);
2847
2939
  allTableData = allTableContent.slice(startIndex, lastIndex);
2848
2940
  } catch(err) {
2849
2941
  if (err.code != 'ENOENT') throw err;
@@ -2920,29 +3012,39 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2920
3012
  }else{
2921
3013
  var lines=content.split(/\r?\n/)
2922
3014
  .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))) )
3015
+ .map(line => splitRawRowIntoRow(line))
2924
3016
  .filter(line => line.length>1 || line.length==1 && line[0].trim() );
2925
3017
  if(lines.length>1){
2926
3018
  if(lines[0][0].startsWith('\ufeff')){
2927
3019
  lines[0][0]=lines[0][0].replace('\ufeff','')
2928
3020
  }
2929
3021
  rows=lines.slice(1);
3022
+ var filteredFieldDef = lines[0].filter(filterField);
2930
3023
  if(tablesWithStrictSequence[tableName]){
2931
3024
  var dataString="COPY "+db.quoteIdent(tableName)+" ("+
2932
- lines[0].filter(filterField).map(db.quoteIdent).join(', ')+
3025
+ filteredFieldDef.map(db.quoteIdent).join(', ')+
2933
3026
  ') FROM stdin;\n'+
2934
3027
  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');
3028
+ return line.filter(function(_,i){ return filterField(lines[0][i]);}).map(function(value,i){
3029
+ var def = tableDef.field[filteredFieldDef[i]];
3030
+ return value==='' ? (
3031
+ def.allowEmptyText && ('nullable' in def) && !def.nullable ? '' : '\\N'
3032
+ ): value.replace(/\\/g,'\\\\').replace(/\t/g,'\\t').replace(/\n/g,'\\n').replace(/\r/g,'\\r');
2937
3033
  }).join('\t')+'\n';
2938
3034
  }).join('')+'\\.\n';
2939
3035
  }else{
2940
3036
  var dataString="insert into "+db.quoteIdent(tableName)+" ("+
2941
- lines[0].filter(filterField).map(db.quoteIdent).join(', ')+
3037
+ filteredFieldDef.map(db.quoteIdent).join(', ')+
2942
3038
  ') values\n'+
2943
3039
  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);
3040
+ return "("+line.filter(function(_,i){ return filterField(lines[0][i]);}).map(function(value,i){
3041
+ var def = tableDef.field[filteredFieldDef[i]];
3042
+ if(def == null) {
3043
+ throw Error("no se encuentra la columna "+filteredFieldDef[i]+" en "+tableName);
3044
+ }
3045
+ return value==='' ? (
3046
+ def.allowEmptyText && ('nullable' in def) && !def.nullable ? "''" : 'null'
3047
+ ) : db.quoteNullable(value);
2946
3048
  }).join(', ')+")";
2947
3049
  }).join(',\n')+';\n';
2948
3050
  }
@@ -2965,28 +3067,65 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2965
3067
  console.log('silence "skipping content" messages in "local-config.yaml".install.dump.skip-content=true');
2966
3068
  }
2967
3069
  }
3070
+ let installFolders = be.config.install.dump.folders ?? ['install']
2968
3071
  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
- });
3072
+ [
3073
+ ['prepare.sql'],
3074
+ ['pre-adapt.sql'].concat(be.config.install.dump.scripts['pre-adapt']),
3075
+ ['adapt.sql'],
3076
+ be.config.install.dump.scripts['prepare'] ?? [],
3077
+ be.config.install.dump.scripts['post-adapt'] ?? []
3078
+ ]
3079
+ .map(async function(fileNames){
3080
+ if (!fileNames) return '';
3081
+ var i = 0;
3082
+ return (await Promise.all(fileNames.map(async fileName => {
3083
+ var content;
3084
+ do {
3085
+ var folder = installFolders[i];
3086
+ try{
3087
+ content = await fs.readFile(be.rootPath+'/'+folder+'/'+fileName, {encoding:'UTF8'});
3088
+ } catch (err) {
3089
+ if(err.code!='ENOENT') throw err;
3090
+ }
3091
+ i++;
3092
+ } while (i < installFolders.length && !content);
3093
+ if (!content) {
3094
+ return '-- no '+fileName+'\n';
3095
+ } else {
3096
+ return '-- '+folder+'/'+fileName+'\n'+content;
3097
+ };
3098
+ }))).join('\n')
2982
3099
  })
2983
3100
  );
3101
+
3102
+ var common = (await Promise.all(be.appStack.map(async function(stackNode){
3103
+ var common = [];
3104
+ for (var prefix of ['../', '../../']) {
3105
+ try {
3106
+ var dirName = Path.join(stackNode.path,prefix+'install').replace(regexpDistReplacer,'$1$2')
3107
+ var list = await fs.readdir(dirName);
3108
+ } catch (err) {
3109
+ if (err.code != 'ENOENT') throw err;
3110
+ var list = [];
3111
+ }
3112
+ for(var fileName of list){
3113
+ if (fileName.endsWith('-fun.sql')) {
3114
+ common.push(await fs.readFile(Path.join(dirName,fileName), 'utf-8'));
3115
+ }
3116
+ }
3117
+ }
3118
+ return common.join('\n');
3119
+ }))).join('\n');
3120
+
2984
3121
  var prepareList=(be.config.install.dump.scripts['prepare']||[]);
2985
3122
  var mainSql=(
2986
3123
  (complete? linesCreate.join('\n'): '')+
2987
3124
  (complete||opts.forDump? searchPathline.join('\n'): '')+
3125
+ '\n-- common'+
3126
+ common+'\n'+
2988
3127
  (complete? '\n\n--prepare.sql\n'+ texts[0]+'\n\n' :'' )+
2989
- (complete? texts.slice(3,3+prepareList.length).join('\n\n')+'\n\n' : '' )+
3128
+ (complete? texts[3] + '\n\n' : '' )+
2990
3129
  '\n-- functions\n' + functionLines.join('\n')+
2991
3130
  '\n-- lines \n' + lines.join('\n')+
2992
3131
  (complete? ('\n\n-- pre-ADAPTs\n'+texts[1]+'\n\n') : '' )+
@@ -2996,7 +3135,7 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2996
3135
  '\n-- FKs\n' + fkLines.join('\n')+
2997
3136
  '\n-- index\n' + indexLines.join('\n')+
2998
3137
  '\n-- policies\n' + policyLines.join('\n')+
2999
- (complete? texts.slice(3+prepareList.length).join('\n\n')+'\n\n' : '' )+
3138
+ (complete? texts[4] + '\n\n' : '' )+
3000
3139
  (complete? (be.config.install.dump.enances==='inline'?enancePart:'') :'')
3001
3140
  ).replace(/\uFEFF/g /*inner BOM replacing*/,'\n\n').replace(
3002
3141
  new RegExp(escapeRegExp(db.quoteIdent(be.config.install.dump.db["owner4special-scripts"])),'g'),
@@ -3117,6 +3256,8 @@ AppBackend.prototype.exportacionesGenerico = async function exportacionesGeneric
3117
3256
  if (typeof result[0].title !== "string" || typeof result[0].rows !== "object" && !(result[0].rows instanceof Array) ) {
3118
3257
  throw new Error ("exportacionesGenerico debe recibir {title:string, rows:Record<string, any>[]}")
3119
3258
  }
3259
+ csvFileName = result[0].csvFileName ?? csvFileName;
3260
+ fileName = result[0].fileName ?? fileName;
3120
3261
  await bestGlobals.sleep(100);
3121
3262
  context.informProgress({message:`buscando archivos recién generados`})
3122
3263
  /** @type {{url:string, label:string}[]} */