backend-plus 1.15.2 → 1.16.2

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.
@@ -79,10 +79,14 @@ myOwn.i18n.messages.en=changing(myOwn.i18n.messages.en, {
79
79
  notSimilarTo:'not similar to',
80
80
  numberExportedRows:"Rows exported",
81
81
  oldValue: "old value",
82
+ oneRowDeleted: "one row deleted.",
83
+ xRowsDeleted: "{$x} rows deleted.",
82
84
  oneRowInserted: "one row inserted.",
83
85
  xRowsInserted: "{$x} rows inserted.",
84
86
  oneRowUpdated: "one row updated.",
85
87
  xRowsUpdated: "{$x} rows updated.",
88
+ oneRowSkipped: "one skipped row",
89
+ xRowsSkipped: "{$x} skipped row",
86
90
  optionsForThisTable: "options for this table",
87
91
  orientationToggle: "toggle orientation (vertical vs horizontal)",
88
92
  prepare: "prepare",
@@ -143,10 +147,14 @@ myOwn.i18n.messages.es=changing(myOwn.i18n.messages.es, {
143
147
  notSimilarTo:'no contiene',
144
148
  numberExportedRows:"Filas exportadas",
145
149
  oldValue: "valor anterior",
150
+ oneRowDeleted: "un registro borrado",
151
+ xRowsDeleted: "{$x} registros borrados",
146
152
  oneRowInserted: "un registro insertado.",
147
153
  xRowsInserted: "{$x} registros insertados.",
148
154
  oneRowUpdated: "un registro modificados.",
149
155
  xRowsUpdated: "{$x} registros modificados.",
156
+ oneRowSkipped: "un registro salteado",
157
+ xRowsSkipped: "{$x} registros salteados",
150
158
  optionsForThisTable: "opciones para esta tabla",
151
159
  orientationToggle: "cambiar la orientación de la tabla (por fila o por columna)",
152
160
  prepare: "preparar",
@@ -1336,6 +1344,16 @@ myOwn.TableGrid.prototype.prepareMenu = function prepareMenu(button){
1336
1344
  }else if(result.uploaded.updated>1){
1337
1345
  messages.push(my.messages.xRowsUpdated.replace('{$x}',result.uploaded.updated));
1338
1346
  }
1347
+ if(result.uploaded.skipped==1){
1348
+ messages.push(my.messages.oneRowSkipped);
1349
+ }else if(result.uploaded.skipped>1){
1350
+ messages.push(my.messages.xRowsSkipped.replace('{$x}',result.uploaded.skipped));
1351
+ }
1352
+ if(result.uploaded.deleted==1){
1353
+ messages.push(my.messages.oneRowDeleted);
1354
+ }else if(result.uploaded.deleted>1){
1355
+ messages.push(my.messages.xRowsDeleted.replace('{$x}',result.uploaded.deleted));
1356
+ }
1339
1357
  if(result.uploaded.skippedColumns.length==1){
1340
1358
  messages.push(my.messages.skippedColumn+": "+result.uploaded.skippedColumns.join(', '));
1341
1359
  }else if(result.uploaded.skippedColumns.length>1){
@@ -294,6 +294,7 @@ export type TableDefinition = EditableDbDefinition & {
294
294
  firstDisplayOverLimit?:number
295
295
  description?:MarkdownDoc
296
296
  exportJsonFieldAsColumns?:string
297
+ importCuidado?:boolean
297
298
  }
298
299
  export interface DetailTable { table?: string, fields: FieldsForConnectDetailTable, abr: string, label?: string, refreshParent?:boolean, refreshFromParent?:boolean, wScreen?:string, condition?:string }
299
300
  export type TableDefinitionFunction = (context: ContextForDump, opts?:any) => TableDefinition;
@@ -230,7 +230,7 @@ AppBackend.prototype.i18n.messages.en={
230
230
  fileUploaded:'file uploaded',
231
231
  unkownExt:'unkown file extension'
232
232
  },
233
- server:{
233
+ server:{
234
234
  cantDelete_TLDR:'Can\'t delete. May be rights problems, locked records or internal problems',
235
235
  cantInsert_TLDR:'Can\'t insert. May be rights problems, locked records or internal problems',
236
236
  cantUpdate_TLDR:'Can\'t update. May be rights problems, locked records or internal problems',
@@ -250,6 +250,7 @@ AppBackend.prototype.i18n.messages.en={
250
250
  line:'line',
251
251
  missingPrimaryKeyValues:'missing primary key values',
252
252
  noMailerConfig:'not mailer configurated',
253
+ notInputCuidado:'The uploaded file is not a "#cuidado" file',
253
254
  notLoggedIn:'not logged in',
254
255
  oldPassDontMatch:'old password does not matchs or username is not longer valid or token is invalid',
255
256
  passChangedSuccesfully: 'password was changed successfully',
@@ -327,6 +328,7 @@ AppBackend.prototype.i18n.messages.es={
327
328
  line:'linea',
328
329
  missingPrimaryKeyValues:'faltan valores en los campos de la clave principal',
329
330
  noMailerConfig:'falta la configuración del mailer',
331
+ notInputCuidado:'El archivo no es un archivo "#cuidado"',
330
332
  notLoggedIn:'sin inicio de sesión',
331
333
  oldPassDontMatch:'la clave anterior no coincide o el usuario es inválido o el token está vencido',
332
334
  passChangedSuccesfully: 'password was changed successfully',
@@ -1127,6 +1129,9 @@ AppBackend.prototype.rootPath = process.cwd();
1127
1129
  AppBackend.prototype.specialValueWhenInsert = {
1128
1130
  currentUsername:function(context){
1129
1131
  return context.username;
1132
+ },
1133
+ lineNumberWhenImported:function(_context, _defField, parameters){
1134
+ return parameters.lineNumber
1130
1135
  }
1131
1136
  }
1132
1137
 
@@ -9,6 +9,8 @@ var typeStore=require('type-store');
9
9
  var likeAr=require('like-ar');
10
10
  const f = require('session-file-store');
11
11
 
12
+ const PANIC_IMPORT = true;
13
+
12
14
  var ProcedureTables;
13
15
 
14
16
  function inlineLog(mmm){ console.log(mmm); return mmm; }
@@ -437,9 +439,9 @@ ProcedureTables = [
437
439
  return context.client.query(
438
440
  inlineLog(
439
441
  "DELETE FROM "+be.db.quoteIdent(defTable.sql.tableName)+
440
- " WHERE "+whereParts.join(" AND ")),
442
+ " WHERE "+whereParts.join(" AND ")+" RETURNING 1"),
441
443
  dataParams
442
- ).execute().then(function(){
444
+ ).fetchUniqueRow().then(function(){
443
445
  deleteCounter++;
444
446
  context.informProgress({lengthComputable:true, loaded:deleteCounter, total:parameters.rowsToDelete.length});
445
447
  });
@@ -675,9 +677,12 @@ ProcedureTables = [
675
677
  // console.log('xxxxxxxxxxxxxxx ',files[0].originalFilename);
676
678
  var fields=[];
677
679
  var otherFieldNames=[];
680
+ var inputCuidado = false;
678
681
  var testCandidateColumnAndAddIt=function testCandidateColumnAndAddIt(fieldName, iColumn, cellAddress){
679
682
  cellAddress=cellAddress||iColumn;
680
- if(fieldName && fieldName!='#ignore'){
683
+ if(fieldName == "#cuidado"){
684
+ inputCuidado = true;
685
+ }else if(fieldName && fieldName!='#ignore'){
681
686
  var fieldName=fieldName.trim();
682
687
  if(tableDef.prefix){
683
688
  fieldName=tableDef.prefix+'_'+fieldName;
@@ -737,6 +742,11 @@ ProcedureTables = [
737
742
  "#null":null,
738
743
  "#''":''
739
744
  };
745
+ var controlCuidado = function(){
746
+ if(tableDef.importCuidado && !inputCuidado){
747
+ throw new Error(be.messages.server.notInputCuidado);
748
+ }
749
+ }
740
750
  if(files[0].originalFilename.match(/\.(tab|txt)$/)){
741
751
  readerFunction=fs.readFile(files[0].path,{encoding:'UTF8'}).then(function(content){
742
752
  context.informProgress({message:be.messages.server.fileReaded});
@@ -751,6 +761,7 @@ ProcedureTables = [
751
761
  heading=lines[0].split(separator);
752
762
  }
753
763
  heading.forEach(function(col,i){ return testCandidateColumnAndAddIt(col,i); });
764
+ controlCuidado();
754
765
  doing="retrieving rows";
755
766
  var rowsForUpsert=lines.slice(1).map(function(line){
756
767
  var othersArray = [];
@@ -775,6 +786,9 @@ ProcedureTables = [
775
786
  newRow[field.name]=value;
776
787
  }
777
788
  });
789
+ if(inputCuidado){
790
+ newRow['#cuidado'] = row[0];
791
+ }
778
792
  newRow = addOtherColumnsToNewRow(newRow, othersArray);
779
793
  return newRow;
780
794
  });
@@ -810,6 +824,7 @@ ProcedureTables = [
810
824
  testCandidateColumnAndAddIt(cellOfFieldName.v,iColumn,cellAddress);
811
825
  }
812
826
  }
827
+ controlCuidado();
813
828
  doing="retrieving rows";
814
829
  var rowsForUpsert=[];
815
830
  for(var iRow=fieldNameRowNum+1; iRow<=range.e.r; iRow++){
@@ -854,6 +869,9 @@ ProcedureTables = [
854
869
  }
855
870
  }
856
871
  };
872
+ if(inputCuidado){
873
+ newRow['#cuidado'] = (ws[XLSX.utils.encode_cell({r:iRow, c:0})]||{}).v;
874
+ }
857
875
  newRow = addOtherColumnsToNewRow(newRow, othersArray);
858
876
  rowsForUpsert.push(newRow);
859
877
  }
@@ -865,7 +883,9 @@ ProcedureTables = [
865
883
  };
866
884
  var inserted=0;
867
885
  var updated=0;
886
+ var deleted=0;
868
887
  var readed=0;
888
+ var skipped=0;
869
889
  var total=1;
870
890
  var origin=new Date();
871
891
  if(tableDef.sql.constraintsDeferred){
@@ -873,6 +893,7 @@ ProcedureTables = [
873
893
  await context.client.query("SET CONSTRAINTS ALL DEFERRED").execute();
874
894
  }
875
895
  return readerFunction.then(async function(rowsAndOtherFieldsNames){
896
+ var importErrors = [];
876
897
  var rowsForUpsert = rowsAndOtherFieldsNames.rowsForUpsert;
877
898
  total = rowsForUpsert.length;
878
899
  var otherFieldNames = rowsAndOtherFieldsNames.otherFieldNames;
@@ -881,6 +902,19 @@ ProcedureTables = [
881
902
  var previousRow={};
882
903
  let lineNumber = 1;
883
904
  for(let newRow of rowsForUpsert){
905
+ var { procedure, insertIfNotUpdate, status } = ({
906
+ false :{procedure:'table_record_save' , status:'update', insertIfNotUpdate: true },
907
+ ['#nuevo' ]:{procedure:'table_record_save' , status:'new' , insertIfNotUpdate: false},
908
+ ['#cambio']:{procedure:'table_record_save' , status:'update', insertIfNotUpdate: false},
909
+ ['#borrar']:{procedure:'table_record_delete', status:'update', insertIfNotUpdate: false},
910
+ })[ inputCuidado && newRow['#cuidado'] ] || {}
911
+ if(inputCuidado){
912
+ delete newRow['#cuidado'];
913
+ }
914
+ if(procedure == null){
915
+ skipped++;
916
+ continue;
917
+ }
884
918
  var skip=false;
885
919
  var hasOneValue=false;
886
920
  var primaryKeyValues=[];
@@ -915,7 +949,7 @@ ProcedureTables = [
915
949
  }
916
950
  if(field.isPk){
917
951
  primaryKeyValues[field.isPk-1]=value;
918
- if(value==null){
952
+ if(value==null && !defField.specialValueWhenInsert){
919
953
  throw new Error(be.messages.server.primarKeyCantBeNull4.replace('$1',lineNumber).replace('$2',field.name));
920
954
  }
921
955
  }
@@ -944,16 +978,17 @@ ProcedureTables = [
944
978
  readed++;
945
979
  context.informProgress({lengthComputable:true, loaded:readed, total:total});
946
980
  try{
947
- var result = await be.procedure.table_record_save.coreFunction(
981
+ var result = await be.procedure[procedure].coreFunction(
948
982
  context,
949
983
  {
950
984
  table: parameters.table,
951
985
  primaryKeyValues,
952
986
  newRow,
953
987
  oldRow:[],
954
- status:'update',
955
- insertIfNotUpdate:true,
956
- masive:true
988
+ status,
989
+ insertIfNotUpdate,
990
+ masive:true,
991
+ lineNumber
957
992
  },
958
993
  null,
959
994
  {forImport:true}
@@ -962,10 +997,28 @@ ProcedureTables = [
962
997
  inserted++;
963
998
  }else if(result.command==='UPDATE'){
964
999
  updated++;
1000
+ }else if(procedure=='table_record_delete'){
1001
+ deleted++;
965
1002
  }
966
1003
  }catch(err){
967
1004
  err.detail=be.messages.server.line+' '+lineNumber+' / '+tableDef.primaryKey+' = '+JSON.stringify(primaryKeyValues);
968
- throw err;
1005
+ if(PANIC_IMPORT){
1006
+ throw err;
1007
+ }else{
1008
+ importErrors.push(err);
1009
+ try{
1010
+ await context.client.query('ROLLBACK').execute();
1011
+ }catch(err2){
1012
+ err.detail+=' *** in ROLLBACK ' + err2.message;
1013
+ throw err
1014
+ }
1015
+ try{
1016
+ await context.client.query("BEGIN TRANSACTION").execute();
1017
+ }catch(err2){
1018
+ err.detail+=' *** in BEGIN TRANSACTION ' + err2.message;
1019
+ throw err
1020
+ }
1021
+ }
969
1022
  };
970
1023
  }
971
1024
  previousRow=newRow;
@@ -987,28 +1040,32 @@ ProcedureTables = [
987
1040
  for(let pkOtherField of otherFieldsTableDef.primaryKey){
988
1041
  primaryKeyValues.push(newRow[pkOtherField]);
989
1042
  };
990
- await be.procedure.table_record_save.coreFunction(
1043
+ await be.procedure[procedure].coreFunction(
991
1044
  context,{
992
1045
  table: otherFieldsTableDef.name,
993
1046
  primaryKeyValues,
994
1047
  newRow,
995
1048
  oldRow:[],
996
- status:'update',
997
- insertIfNotUpdate:true,
1049
+ status,
1050
+ insertIfNotUpdate,
998
1051
  masive:true
999
1052
  }
1000
1053
  );
1001
1054
  };
1002
1055
  }
1056
+ if(importErrors.length){
1057
+ await context.client.query('ROLLBACK').execute();
1058
+ return {json:{importErrors}};
1059
+ }
1003
1060
  context.informProgress({message:be.messages.insertingRows});
1004
- }).then(async function(){
1061
+ }).then(async function(other){
1005
1062
  context.informProgress({message:be.messages.checkingConstraints});
1006
1063
  if(tableDef.sql.constraintsDeferred){
1007
1064
  await context.client.query("SET CONSTRAINTS ALL IMMEDIATE").execute();
1008
1065
  await recreateConstraints(tableDef, true);
1009
1066
  }
1010
1067
  context.informProgress({lengthComputable:true, loaded:readed, total:total, force:true});
1011
- return {uploaded:{inserted,updated,skippedColumns}};
1068
+ return other || {uploaded:{inserted,updated,skipped,deleted,skippedColumns}};
1012
1069
  });
1013
1070
  }).catch(function(err){
1014
1071
  console.log('ERROR',err.message);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "backend-plus",
3
3
  "description": "Backend for typed controls",
4
- "version": "1.15.2",
4
+ "version": "1.16.2",
5
5
  "author": "Codenautas <codenautas@googlegroups.com>",
6
6
  "license": "MIT",
7
7
  "repository": "codenautas/backend-plus",
@@ -32,7 +32,7 @@
32
32
  "backend-skins": "^0.1.15",
33
33
  "best-globals": "^1.0.3",
34
34
  "big.js": "^6.1.1",
35
- "body-parser": "^1.19.2",
35
+ "body-parser": "^1.20.0",
36
36
  "cast-error": "^0.1.0",
37
37
  "castellano": "^0.1.3",
38
38
  "connect-pg-simple": "^7.0.0",
@@ -52,8 +52,8 @@
52
52
  "login-plus": "^1.6.2",
53
53
  "memorystore": "^1.6.7",
54
54
  "mini-tools": "^1.11.2",
55
- "nodemailer": "^6.7.2",
56
- "moment": "^2.29.1",
55
+ "nodemailer": "^6.7.3",
56
+ "moment": "^2.29.2",
57
57
  "multiparty": "^4.2.3",
58
58
  "numeral": "^2.0.6",
59
59
  "pg-promise-strict": "^1.2.6",
@@ -65,12 +65,12 @@
65
65
  "self-explain": "^0.10.22",
66
66
  "serve-content": "^0.3.17",
67
67
  "session-file-store": "^1.5.0",
68
- "sql-tools": "^0.1.0",
68
+ "sql-tools": "^0.1.2",
69
69
  "stack-trace": "^0.0.10",
70
- "stylus": "^0.56.0",
70
+ "stylus": "^0.57.0",
71
71
  "type-store": "^0.2.41",
72
72
  "typed-controls": "^0.10.0",
73
- "xlsx": "^0.18.3",
73
+ "xlsx": "^0.18.5",
74
74
  "xlsx-style": "^0.8.13"
75
75
  },
76
76
  "devDependencies": {
@@ -83,7 +83,7 @@
83
83
  "@types/mocha": "^9.1.0",
84
84
  "@types/nodemailer": "^6.4.4",
85
85
  "@types/multiparty": "~0.0.33",
86
- "@types/node": "^17.0.21",
86
+ "@types/node": "^17.0.23",
87
87
  "@types/numeral": "~2.0.2",
88
88
  "@types/session-file-store": "^1.2.2",
89
89
  "@types/stack-trace": "~0.0.29",
@@ -91,19 +91,19 @@
91
91
  "esprima": "^4.0.1",
92
92
  "expect.js": "~0.3.1",
93
93
  "karma": "6.3.17",
94
- "karma-chrome-launcher": "^3.1.0",
94
+ "karma-chrome-launcher": "^3.1.1",
95
95
  "karma-expect": "^1.1.3",
96
96
  "karma-firefox-launcher": "^2.1.2",
97
97
  "karma-ie-launcher": "^1.0.0",
98
98
  "karma-mocha": "^2.0.1",
99
99
  "kill-9": "~0.4.3",
100
- "mocha": "^9.2.1",
100
+ "mocha": "^9.2.2",
101
101
  "nyc": "^15.1.0",
102
- "puppeteer": "^13.4.1",
102
+ "puppeteer": "^13.5.2",
103
103
  "sinon": "^13.0.1",
104
104
  "supertest": "^6.2.2",
105
105
  "types.d.ts": "~0.6.7",
106
- "typescript": "^4.6.2",
106
+ "typescript": "^4.6.3",
107
107
  "why-is-node-running": "^2.2.1"
108
108
  },
109
109
  "engines": {
@@ -117,6 +117,7 @@
117
117
  "test-good": "mocha --reporter spec --bail --check-leaks test/test*.js",
118
118
  "example-pu": "node test/puppeteer/first-step.js",
119
119
  "test-pu": "mocha --reporter spec --bail --check-leaks --globals cptable --globals QUOTE --globals __core-js_shared__ test/test-pu.js",
120
+ "test-server": "mocha --reporter spec --single-run --bail test/test.js",
120
121
  "server-test": "node test/run-simple-backend.js",
121
122
  "example-tables": "node examples/tables/server/server-tables.js --dir-x examples/tables",
122
123
  "example-fichas": "node examples/fichero/server/server-fichas.js",
@@ -1,15 +0,0 @@
1
- /**
2
- * @function
3
- * @template T extends {}
4
- * @param {T & {name?:string|null|undefined}} obj
5
- * @param {string} n
6
- * @return {obj is T}
7
- */
8
- function addName(obj, n) {
9
- obj.name = n;
10
- }
11
-
12
- let thing = {size: "medium"};
13
- addName(thing, "product");
14
- var other = thing;
15
- console.log(thing.name);