@saltcorn/server 0.8.0-beta.2 → 0.8.0-beta.4

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.
package/routes/tables.js CHANGED
@@ -83,33 +83,33 @@ const tableForm = async (table, req) => {
83
83
  fields: [
84
84
  ...(!table.external
85
85
  ? [
86
- {
87
- label: req.__("Ownership field"),
88
- name: "ownership_field_id",
89
- sublabel: req.__(
90
- "The user referred to in this field will be the owner of the row"
91
- ),
92
- input_type: "select",
93
- options: [
94
- { value: "", label: req.__("None") },
95
- ...userFields,
96
- { value: "_formula", label: req.__("Formula") },
97
- ],
98
- },
99
- {
100
- name: "ownership_formula",
101
- label: req.__("Ownership formula"),
102
- validator: expressionValidator,
103
- type: "String",
104
- class: "validate-expression",
105
- sublabel:
106
- req.__("User is treated as owner if true. In scope: ") +
107
- ["user", ...fields.map((f) => f.name)]
108
- .map((fn) => code(fn))
109
- .join(", "),
110
- showIf: { ownership_field_id: "_formula" },
111
- },
112
- ]
86
+ {
87
+ label: req.__("Ownership field"),
88
+ name: "ownership_field_id",
89
+ sublabel: req.__(
90
+ "The user referred to in this field will be the owner of the row"
91
+ ),
92
+ input_type: "select",
93
+ options: [
94
+ { value: "", label: req.__("None") },
95
+ ...userFields,
96
+ { value: "_formula", label: req.__("Formula") },
97
+ ],
98
+ },
99
+ {
100
+ name: "ownership_formula",
101
+ label: req.__("Ownership formula"),
102
+ validator: expressionValidator,
103
+ type: "String",
104
+ class: "validate-expression",
105
+ sublabel:
106
+ req.__("User is treated as owner if true. In scope: ") +
107
+ ["user", ...fields.map((f) => f.name)]
108
+ .map((fn) => code(fn))
109
+ .join(", "),
110
+ showIf: { ownership_field_id: "_formula" },
111
+ },
112
+ ]
113
113
  : []),
114
114
  // description of table
115
115
  {
@@ -129,29 +129,29 @@ const tableForm = async (table, req) => {
129
129
  name: "min_role_read",
130
130
  input_type: "select",
131
131
  options: roleOptions,
132
- attributes: { asideNext: !table.external }
132
+ attributes: { asideNext: !table.external },
133
133
  },
134
134
  ...(table.external
135
135
  ? []
136
136
  : [
137
- {
138
- label: req.__("Minimum role to write"),
139
- name: "min_role_write",
140
- input_type: "select",
141
- sublabel: req.__(
142
- "User must have this role or higher to edit or create new rows in the table, unless they are the owner"
143
- ),
144
- options: roleOptions,
145
- },
146
- {
147
- label: req.__("Version history"),
148
- sublabel: req.__(
149
- "Version history allows to track table data changes"
150
- ),
151
- name: "versioned",
152
- type: "Bool",
153
- },
154
- ]),
137
+ {
138
+ label: req.__("Minimum role to write"),
139
+ name: "min_role_write",
140
+ input_type: "select",
141
+ sublabel: req.__(
142
+ "User must have this role or higher to edit or create new rows in the table, unless they are the owner"
143
+ ),
144
+ options: roleOptions,
145
+ },
146
+ {
147
+ label: req.__("Version history"),
148
+ sublabel: req.__(
149
+ "Version history allows to track table data changes"
150
+ ),
151
+ name: "versioned",
152
+ type: "Bool",
153
+ },
154
+ ]),
155
155
  ],
156
156
  });
157
157
  if (table) {
@@ -218,11 +218,11 @@ const discoverForm = (tables, req) => {
218
218
  blurb:
219
219
  tables.length > 0
220
220
  ? req.__(
221
- "The following tables in your database can be imported into Saltcorn:"
222
- )
221
+ "The following tables in your database can be imported into Saltcorn:"
222
+ )
223
223
  : req.__(
224
- "There are no tables in the database that can be imported into Saltcorn."
225
- ),
224
+ "There are no tables in the database that can be imported into Saltcorn."
225
+ ),
226
226
  submitLabel: req.__("Import"),
227
227
  fields: tables.map((t) => ({
228
228
  name: t.table_name,
@@ -328,6 +328,7 @@ router.get(
328
328
  name: "name",
329
329
  input_type: "text",
330
330
  },
331
+ // todo implement file mask filter like , accept: "text/csv"
331
332
  { label: req.__("File"), name: "file", input_type: "file" },
332
333
  ],
333
334
  }),
@@ -353,7 +354,7 @@ router.get(
353
354
  */
354
355
  router.post(
355
356
  "/create-from-csv",
356
- setTenant, // TODO why is this needed?????
357
+ setTenant,
357
358
  isAdmin,
358
359
  error_catcher(async (req, res) => {
359
360
  if (req.body.name && req.files && req.files.file) {
@@ -515,7 +516,12 @@ const attribBadges = (f) => {
515
516
  let s = "";
516
517
  if (f.attributes) {
517
518
  Object.entries(f.attributes).forEach(([k, v]) => {
518
- if (["summary_field", "default"].includes(k)) return;
519
+ if (
520
+ ["summary_field", "default", "on_delete_cascade", "on_delete"].includes(
521
+ k
522
+ )
523
+ )
524
+ return;
519
525
  if (v || v === 0) s += badge("secondary", k);
520
526
  });
521
527
  }
@@ -574,11 +580,11 @@ router.get(
574
580
  key: (r) =>
575
581
  r.type === "Key"
576
582
  ? `Key to ` +
577
- a({ href: `/table/${r.reftable_name}` }, r.reftable_name)
583
+ a({ href: `/table/${r.reftable_name}` }, r.reftable_name)
578
584
  : (r.type && r.type.name) ||
579
- r.type ||
580
- r.typename +
581
- span({ class: "badge bg-danger ms-1" }, "Unknown type"),
585
+ r.type ||
586
+ r.typename +
587
+ span({ class: "badge bg-danger ms-1" }, "Unknown type"),
582
588
  },
583
589
  {
584
590
  label: "",
@@ -592,23 +598,23 @@ router.get(
592
598
  ...(table.external
593
599
  ? []
594
600
  : [
595
- {
596
- label: req.__("Edit"),
597
- key: (r) => link(`/field/${r.id}`, req.__("Edit")),
598
- },
599
- ]),
601
+ {
602
+ label: req.__("Edit"),
603
+ key: (r) => link(`/field/${r.id}`, req.__("Edit")),
604
+ },
605
+ ]),
600
606
  ...(table.external || db.isSQLite
601
607
  ? []
602
608
  : [
603
- {
604
- label: req.__("Delete"),
605
- key: (r) =>
606
- (table.name === "users" && r.name === "email") ||
609
+ {
610
+ label: req.__("Delete"),
611
+ key: (r) =>
612
+ (table.name === "users" && r.name === "email") ||
607
613
  r.primary_key
608
- ? ""
609
- : post_delete_btn(`/field/delete/${r.id}`, req, r.name),
610
- },
611
- ]),
614
+ ? ""
615
+ : post_delete_btn(`/field/delete/${r.id}`, req, r.name),
616
+ },
617
+ ]),
612
618
  ],
613
619
  fields,
614
620
  { hover: true }
@@ -617,17 +623,17 @@ router.get(
617
623
  tableHtml,
618
624
  inbound_refs.length > 0
619
625
  ? req.__("Inbound keys: ") +
620
- inbound_refs.map((tnm) => link(`/table/${tnm}`, tnm)).join(", ") +
621
- "<br>"
626
+ inbound_refs.map((tnm) => link(`/table/${tnm}`, tnm)).join(", ") +
627
+ "<br>"
622
628
  : "",
623
629
  !table.external &&
624
- a(
625
- {
626
- href: `/field/new/${table.id}`,
627
- class: "btn btn-primary add-field mt-2",
628
- },
629
- req.__("Add field")
630
- ),
630
+ a(
631
+ {
632
+ href: `/field/new/${table.id}`,
633
+ class: "btn btn-primary add-field mt-2",
634
+ },
635
+ req.__("Add field")
636
+ ),
631
637
  ];
632
638
  }
633
639
  var viewCard;
@@ -699,8 +705,8 @@ router.get(
699
705
  table.name === "users"
700
706
  ? `/useradmin/`
701
707
  : fields.length === 1
702
- ? `javascript:;` // Fix problem with edition of table with only one column ID / Primary Key
703
- : `/list/${table.name}`,
708
+ ? `javascript:;` // Fix problem with edition of table with only one column ID / Primary Key
709
+ : `/list/${table.name}`,
704
710
  },
705
711
  i({ class: "fas fa-2x fa-edit" }),
706
712
  "<br/>",
@@ -720,74 +726,75 @@ router.get(
720
726
  )
721
727
  ),
722
728
  !table.external &&
723
- div(
724
- { class: "mx-auto" },
725
- form(
726
- {
727
- method: "post",
728
- action: `/table/upload_to_table/${table.name}`,
729
- encType: "multipart/form-data",
730
- },
731
- input({ type: "hidden", name: "_csrf", value: req.csrfToken() }),
732
- label(
733
- { class: "btn-link", for: "upload_to_table" },
734
- i({ class: "fas fa-2x fa-upload" }),
735
- "<br/>",
736
- req.__("Upload CSV")
737
- ),
738
- input({
739
- id: "upload_to_table",
740
- name: "file",
741
- type: "file",
742
- accept: "text/csv,.csv",
743
- onchange: "this.form.submit();",
744
- })
745
- )
746
- ),
747
- // only if table is not external
748
- !table.external &&
749
- div(
750
- { class: "mx-auto" },
751
- settingsDropdown(`dataMenuButton`, [
752
- a(
729
+ div(
730
+ { class: "mx-auto" },
731
+ form(
753
732
  {
754
- class: "dropdown-item",
755
- href: `/table/constraints/${table.id}`,
733
+ method: "post",
734
+ action: `/table/upload_to_table/${table.name}`,
735
+ encType: "multipart/form-data",
736
+ acceptCharset: "UTF-8",
756
737
  },
757
- '<i class="fas fa-ban"></i>&nbsp;' + req.__("Constraints")
758
- ),
759
- // rename table doesnt supported for sqlite
760
- !db.isSQLite &&
761
- table.name !== "users" &&
762
- a(
763
- {
764
- class: "dropdown-item",
765
- href: `/table/rename/${table.id}`,
766
- },
767
- '<i class="fas fa-edit"></i>&nbsp;' + req.__("Rename table")
768
- ),
769
- post_dropdown_item(
770
- `/table/recalc-stored/${table.name}`,
771
- '<i class="fas fa-sync"></i>&nbsp;' +
772
- req.__("Recalculate stored fields"),
773
- req
774
- ),
775
- post_dropdown_item(
776
- `/table/delete-all-rows/${table.name}`,
777
- '<i class="far fa-trash-alt"></i>&nbsp;' +
778
- req.__("Delete all rows"),
779
- req,
780
- true
781
- ),
782
- table.name !== "users" &&
783
- post_dropdown_item(
784
- `/table/forget-table/${table.id}`,
785
- '<i class="fas fa-recycle"></i>&nbsp;' + req.__("Forget table"),
786
- req,
787
- true
788
- ),
789
- ])
790
- )
738
+ input({ type: "hidden", name: "_csrf", value: req.csrfToken() }),
739
+ label(
740
+ { class: "btn-link", for: "upload_to_table" },
741
+ i({ class: "fas fa-2x fa-upload" }),
742
+ "<br/>",
743
+ req.__("Upload CSV")
744
+ ),
745
+ input({
746
+ id: "upload_to_table",
747
+ name: "file",
748
+ type: "file",
749
+ accept: "text/csv,.csv",
750
+ onchange: "this.form.submit();",
751
+ })
752
+ )
753
+ ),
754
+ // only if table is not external
755
+ !table.external &&
756
+ div(
757
+ { class: "mx-auto" },
758
+ settingsDropdown(`dataMenuButton`, [
759
+ a(
760
+ {
761
+ class: "dropdown-item",
762
+ href: `/table/constraints/${table.id}`,
763
+ },
764
+ '<i class="fas fa-ban"></i>&nbsp;' + req.__("Constraints")
765
+ ),
766
+ // rename table doesnt supported for sqlite
767
+ !db.isSQLite &&
768
+ table.name !== "users" &&
769
+ a(
770
+ {
771
+ class: "dropdown-item",
772
+ href: `/table/rename/${table.id}`,
773
+ },
774
+ '<i class="fas fa-edit"></i>&nbsp;' + req.__("Rename table")
775
+ ),
776
+ post_dropdown_item(
777
+ `/table/recalc-stored/${table.name}`,
778
+ '<i class="fas fa-sync"></i>&nbsp;' +
779
+ req.__("Recalculate stored fields"),
780
+ req
781
+ ),
782
+ post_dropdown_item(
783
+ `/table/delete-all-rows/${table.name}`,
784
+ '<i class="far fa-trash-alt"></i>&nbsp;' +
785
+ req.__("Delete all rows"),
786
+ req,
787
+ true
788
+ ),
789
+ table.name !== "users" &&
790
+ post_dropdown_item(
791
+ `/table/forget-table/${table.id}`,
792
+ '<i class="fas fa-recycle"></i>&nbsp;' + req.__("Forget table"),
793
+ req,
794
+ true
795
+ ),
796
+ ])
797
+ )
791
798
  );
792
799
  // add table form
793
800
  if (table.ownership_formula && !table.ownership_field_id)
@@ -810,12 +817,12 @@ router.get(
810
817
  },
811
818
  ...(fields.length > 0
812
819
  ? [
813
- {
814
- type: "card",
815
- title: req.__("Table data"),
816
- contents: dataCard,
817
- },
818
- ]
820
+ {
821
+ type: "card",
822
+ title: req.__("Table data"),
823
+ contents: dataCard,
824
+ },
825
+ ]
819
826
  : []),
820
827
  ...(viewCard ? [viewCard] : []),
821
828
  {
@@ -862,7 +869,7 @@ router.post(
862
869
  // todo check that works after where change
863
870
  // todo findOne can be have parameter for external table here
864
871
  //we can only save min role
865
- const table = await Table.findOne( { name : v.name });
872
+ const table = await Table.findOne({ name: v.name });
866
873
  if (table) {
867
874
  const exttables_min_role_read = getState().getConfigCopy(
868
875
  "exttables_min_role_read",
@@ -881,13 +888,13 @@ router.post(
881
888
  const table = await Table.findOne({ id: parseInt(id) });
882
889
  const old_versioned = table.versioned;
883
890
  let hasError = false;
884
- let notify = ""
891
+ let notify = "";
885
892
  if (!rest.versioned) rest.versioned = false;
886
893
  if (rest.ownership_field_id === "_formula") {
887
894
  rest.ownership_field_id = null;
888
895
  const fmlValidRes = expressionValidator(rest.ownership_formula);
889
896
  if (typeof fmlValidRes === "string") {
890
- notify = req.__(`Invalid ownership formula: %s`, fmlValidRes)
897
+ notify = req.__(`Invalid ownership formula: %s`, fmlValidRes);
891
898
  hasError = true;
892
899
  }
893
900
  } else rest.ownership_formula = null;
@@ -1021,16 +1028,18 @@ router.get(
1021
1028
  req.__("Create from CSV upload")
1022
1029
  ),
1023
1030
  !db.isSQLite &&
1024
- a(
1025
- {
1026
- href: `/table/discover`,
1027
- class: "btn btn-secondary mt-1",
1028
- title: req.__("Discover tables that are already in the Database, but not known to Saltcorn"),
1029
- },
1030
- i({ class: "fas fa-map-signs me-1" }),
1031
+ a(
1032
+ {
1033
+ href: `/table/discover`,
1034
+ class: "btn btn-secondary mt-1",
1035
+ title: req.__(
1036
+ "Discover tables that are already in the Database, but not known to Saltcorn"
1037
+ ),
1038
+ },
1039
+ i({ class: "fas fa-map-signs me-1" }),
1031
1040
 
1032
- req.__("Discover tables")
1033
- )
1041
+ req.__("Discover tables")
1042
+ )
1034
1043
  );
1035
1044
  res.sendWrap(req.__("Tables"), {
1036
1045
  above: [
package/routes/utils.js CHANGED
@@ -73,6 +73,8 @@ function isAdmin(req, res, next) {
73
73
  const setLanguage = (req, res, state) => {
74
74
  if (req.user && req.user.language) {
75
75
  req.setLocale(req.user.language);
76
+ } else if (req.cookies?.lang) {
77
+ req.setLocale(req.cookies?.lang);
76
78
  }
77
79
  set_custom_http_headers(res, state);
78
80
  };
@@ -144,8 +146,7 @@ const setTenant = (req, res, next) => {
144
146
  next();
145
147
  });
146
148
  }
147
- }
148
- else {
149
+ } else {
149
150
  setLanguage(req, res);
150
151
  next();
151
152
  }
package/routes/view.js CHANGED
@@ -71,8 +71,11 @@ router.get(
71
71
  title: view.name,
72
72
  what: req.__("View"),
73
73
  url: `/viewedit/edit/${encodeURIComponent(view.name)}`,
74
+ cfgUrl: `/viewedit/config/${encodeURIComponent(view.name)}`,
74
75
  contents,
75
76
  req,
77
+ viewtemplate: view.viewtemplate,
78
+ table: view.table_id || view.exttable_name,
76
79
  })
77
80
  );
78
81
  })
@@ -104,13 +107,17 @@ router.post(
104
107
  if (!row) {
105
108
  if (!table)
106
109
  // todo check after where change
107
- table = await Table.findOne(view.table_id ? { id : view.table_id } : {name: view.exttable_name});
110
+ table = await Table.findOne(
111
+ view.table_id
112
+ ? { id: view.table_id }
113
+ : { name: view.exttable_name }
114
+ );
108
115
  row = await table.getRow({});
109
116
  }
110
117
  if (row) query[sf.name] = row[sf.name];
111
118
  }
112
119
  }
113
- const contents = await view.run(query, { req, res });
120
+ const contents = await view.run(query, { req, res, isPreview: true });
114
121
 
115
122
  res.send(contents);
116
123
  })
package/s3storage.js CHANGED
@@ -1,12 +1,10 @@
1
- var aws = require("aws-sdk");
2
- var express = require("express");
3
- var multer = require("multer");
4
- var multerS3 = require("multer-s3");
1
+ const aws = require("aws-sdk");
2
+ const multer = require("multer");
3
+ const multerS3 = require("multer-s3");
5
4
  const { getState } = require("@saltcorn/data/db/state");
6
5
  const fileUpload = require("express-fileupload");
7
6
  const { v4: uuidv4 } = require("uuid");
8
- const { create } = require("@saltcorn/data/models/file");
9
- var contentDisposition = require("content-disposition");
7
+ const contentDisposition = require("content-disposition");
10
8
 
11
9
  function createS3Client() {
12
10
  return new aws.S3({
@@ -47,11 +45,26 @@ module.exports = {
47
45
 
48
46
  s3upload(req, res, next);
49
47
  } else {
50
- // Use regular file upload
48
+ // Use regular file upload https://www.npmjs.com/package/express-fileupload
49
+ const fileSizeLimit = getState().getConfig("file_upload_limit", 0);
51
50
  fileUpload({
52
51
  useTempFiles: true,
53
52
  createParentPath: true,
54
53
  tempFileDir: "/tmp/",
54
+ // set to true - if you want to have debug
55
+ debug: getState().getConfig("file_upload_debug",false),
56
+ //uriDecodeFileNames: true,
57
+ //safeFileNames: true,
58
+ defCharset: 'utf8',
59
+ defParamCharset: 'utf8',
60
+ // 0 - means no upload limit check
61
+ limits: {
62
+ fileSize: fileSizeLimit,
63
+ },
64
+ abortOnLimit: fileSizeLimit !== 0,
65
+ // 0 - means no upload limit check
66
+ uploadTimeout: getState().getConfig("file_upload_timeout",0),
67
+
55
68
  })(req, res, next);
56
69
  }
57
70
  },
@@ -73,7 +86,7 @@ module.exports = {
73
86
  }
74
87
 
75
88
  // Create S3 object
76
- var s3 = createS3Client();
89
+ const s3 = createS3Client();
77
90
  const bucket = getState().getConfig("storage_s3_bucket");
78
91
 
79
92
  let newFileObject = {};
@@ -122,10 +135,10 @@ module.exports = {
122
135
  */
123
136
  serveObject: function (file, res, download) {
124
137
  if (file.s3_store) {
125
- var s3 = createS3Client();
138
+ const s3 = createS3Client();
126
139
  const bucket = getState().getConfig("storage_s3_bucket");
127
140
 
128
- var params = {
141
+ const params = {
129
142
  Bucket: bucket,
130
143
  Key: file.location,
131
144
  };
@@ -147,7 +160,7 @@ module.exports = {
147
160
 
148
161
  unlinkObject: function (file) {
149
162
  if (file.s3_store) {
150
- var s3 = createS3Client();
163
+ const s3 = createS3Client();
151
164
  return new Promise((resolve, reject) => {
152
165
  s3.deleteObject(
153
166
  {