@saltcorn/server 0.8.0-beta.3 → 0.8.0

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,7 +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
+ // todo implement file mask filter like , accept: "text/csv"
332
332
  { label: req.__("File"), name: "file", input_type: "file" },
333
333
  ],
334
334
  }),
@@ -516,7 +516,12 @@ const attribBadges = (f) => {
516
516
  let s = "";
517
517
  if (f.attributes) {
518
518
  Object.entries(f.attributes).forEach(([k, v]) => {
519
- 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;
520
525
  if (v || v === 0) s += badge("secondary", k);
521
526
  });
522
527
  }
@@ -575,11 +580,11 @@ router.get(
575
580
  key: (r) =>
576
581
  r.type === "Key"
577
582
  ? `Key to ` +
578
- a({ href: `/table/${r.reftable_name}` }, r.reftable_name)
583
+ a({ href: `/table/${r.reftable_name}` }, r.reftable_name)
579
584
  : (r.type && r.type.name) ||
580
- r.type ||
581
- r.typename +
582
- 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"),
583
588
  },
584
589
  {
585
590
  label: "",
@@ -593,23 +598,23 @@ router.get(
593
598
  ...(table.external
594
599
  ? []
595
600
  : [
596
- {
597
- label: req.__("Edit"),
598
- key: (r) => link(`/field/${r.id}`, req.__("Edit")),
599
- },
600
- ]),
601
+ {
602
+ label: req.__("Edit"),
603
+ key: (r) => link(`/field/${r.id}`, req.__("Edit")),
604
+ },
605
+ ]),
601
606
  ...(table.external || db.isSQLite
602
607
  ? []
603
608
  : [
604
- {
605
- label: req.__("Delete"),
606
- key: (r) =>
607
- (table.name === "users" && r.name === "email") ||
609
+ {
610
+ label: req.__("Delete"),
611
+ key: (r) =>
612
+ (table.name === "users" && r.name === "email") ||
608
613
  r.primary_key
609
- ? ""
610
- : post_delete_btn(`/field/delete/${r.id}`, req, r.name),
611
- },
612
- ]),
614
+ ? ""
615
+ : post_delete_btn(`/field/delete/${r.id}`, req, r.name),
616
+ },
617
+ ]),
613
618
  ],
614
619
  fields,
615
620
  { hover: true }
@@ -618,17 +623,17 @@ router.get(
618
623
  tableHtml,
619
624
  inbound_refs.length > 0
620
625
  ? req.__("Inbound keys: ") +
621
- inbound_refs.map((tnm) => link(`/table/${tnm}`, tnm)).join(", ") +
622
- "<br>"
626
+ inbound_refs.map((tnm) => link(`/table/${tnm}`, tnm)).join(", ") +
627
+ "<br>"
623
628
  : "",
624
629
  !table.external &&
625
- a(
626
- {
627
- href: `/field/new/${table.id}`,
628
- class: "btn btn-primary add-field mt-2",
629
- },
630
- req.__("Add field")
631
- ),
630
+ a(
631
+ {
632
+ href: `/field/new/${table.id}`,
633
+ class: "btn btn-primary add-field mt-2",
634
+ },
635
+ req.__("Add field")
636
+ ),
632
637
  ];
633
638
  }
634
639
  var viewCard;
@@ -700,8 +705,8 @@ router.get(
700
705
  table.name === "users"
701
706
  ? `/useradmin/`
702
707
  : fields.length === 1
703
- ? `javascript:;` // Fix problem with edition of table with only one column ID / Primary Key
704
- : `/list/${table.name}`,
708
+ ? `javascript:;` // Fix problem with edition of table with only one column ID / Primary Key
709
+ : `/list/${table.name}`,
705
710
  },
706
711
  i({ class: "fas fa-2x fa-edit" }),
707
712
  "<br/>",
@@ -721,75 +726,75 @@ router.get(
721
726
  )
722
727
  ),
723
728
  !table.external &&
724
- div(
725
- { class: "mx-auto" },
726
- form(
727
- {
728
- method: "post",
729
- action: `/table/upload_to_table/${table.name}`,
730
- encType: "multipart/form-data",
731
- acceptCharset: "UTF-8",
732
- },
733
- input({ type: "hidden", name: "_csrf", value: req.csrfToken() }),
734
- label(
735
- { class: "btn-link", for: "upload_to_table" },
736
- i({ class: "fas fa-2x fa-upload" }),
737
- "<br/>",
738
- req.__("Upload CSV")
739
- ),
740
- input({
741
- id: "upload_to_table",
742
- name: "file",
743
- type: "file",
744
- accept: "text/csv,.csv",
745
- onchange: "this.form.submit();",
746
- })
747
- )
748
- ),
749
- // only if table is not external
750
- !table.external &&
751
- div(
752
- { class: "mx-auto" },
753
- settingsDropdown(`dataMenuButton`, [
754
- a(
729
+ div(
730
+ { class: "mx-auto" },
731
+ form(
755
732
  {
756
- class: "dropdown-item",
757
- 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",
758
737
  },
759
- '<i class="fas fa-ban"></i>&nbsp;' + req.__("Constraints")
760
- ),
761
- // rename table doesnt supported for sqlite
762
- !db.isSQLite &&
763
- table.name !== "users" &&
764
- a(
765
- {
766
- class: "dropdown-item",
767
- href: `/table/rename/${table.id}`,
768
- },
769
- '<i class="fas fa-edit"></i>&nbsp;' + req.__("Rename table")
770
- ),
771
- post_dropdown_item(
772
- `/table/recalc-stored/${table.name}`,
773
- '<i class="fas fa-sync"></i>&nbsp;' +
774
- req.__("Recalculate stored fields"),
775
- req
776
- ),
777
- post_dropdown_item(
778
- `/table/delete-all-rows/${table.name}`,
779
- '<i class="far fa-trash-alt"></i>&nbsp;' +
780
- req.__("Delete all rows"),
781
- req,
782
- true
783
- ),
784
- table.name !== "users" &&
785
- post_dropdown_item(
786
- `/table/forget-table/${table.id}`,
787
- '<i class="fas fa-recycle"></i>&nbsp;' + req.__("Forget table"),
788
- req,
789
- true
790
- ),
791
- ])
792
- )
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
+ )
793
798
  );
794
799
  // add table form
795
800
  if (table.ownership_formula && !table.ownership_field_id)
@@ -812,12 +817,12 @@ router.get(
812
817
  },
813
818
  ...(fields.length > 0
814
819
  ? [
815
- {
816
- type: "card",
817
- title: req.__("Table data"),
818
- contents: dataCard,
819
- },
820
- ]
820
+ {
821
+ type: "card",
822
+ title: req.__("Table data"),
823
+ contents: dataCard,
824
+ },
825
+ ]
821
826
  : []),
822
827
  ...(viewCard ? [viewCard] : []),
823
828
  {
@@ -864,7 +869,7 @@ router.post(
864
869
  // todo check that works after where change
865
870
  // todo findOne can be have parameter for external table here
866
871
  //we can only save min role
867
- const table = await Table.findOne( { name : v.name });
872
+ const table = await Table.findOne({ name: v.name });
868
873
  if (table) {
869
874
  const exttables_min_role_read = getState().getConfigCopy(
870
875
  "exttables_min_role_read",
@@ -883,13 +888,13 @@ router.post(
883
888
  const table = await Table.findOne({ id: parseInt(id) });
884
889
  const old_versioned = table.versioned;
885
890
  let hasError = false;
886
- let notify = ""
891
+ let notify = "";
887
892
  if (!rest.versioned) rest.versioned = false;
888
893
  if (rest.ownership_field_id === "_formula") {
889
894
  rest.ownership_field_id = null;
890
895
  const fmlValidRes = expressionValidator(rest.ownership_formula);
891
896
  if (typeof fmlValidRes === "string") {
892
- notify = req.__(`Invalid ownership formula: %s`, fmlValidRes)
897
+ notify = req.__(`Invalid ownership formula: %s`, fmlValidRes);
893
898
  hasError = true;
894
899
  }
895
900
  } else rest.ownership_formula = null;
@@ -1023,16 +1028,18 @@ router.get(
1023
1028
  req.__("Create from CSV upload")
1024
1029
  ),
1025
1030
  !db.isSQLite &&
1026
- a(
1027
- {
1028
- href: `/table/discover`,
1029
- class: "btn btn-secondary mt-1",
1030
- title: req.__("Discover tables that are already in the Database, but not known to Saltcorn"),
1031
- },
1032
- 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" }),
1033
1040
 
1034
- req.__("Discover tables")
1035
- )
1041
+ req.__("Discover tables")
1042
+ )
1036
1043
  );
1037
1044
  res.sendWrap(req.__("Tables"), {
1038
1045
  above: [
@@ -81,7 +81,7 @@ const formOptions = async (type, tag_id) => {
81
81
  case "triggers": {
82
82
  const ids = await tag.getTriggerIds();
83
83
  return {
84
- triggers: (Trigger.find()).filter(
84
+ triggers: Trigger.find().filter(
85
85
  (value) => ids.indexOf(value.id) === -1
86
86
  ),
87
87
  };
package/routes/utils.js CHANGED
@@ -218,7 +218,9 @@ const scan_for_page_title = (contents, viewname) => {
218
218
  try {
219
219
  scanstr =
220
220
  typeof contents === "string" ? contents : JSON.stringify(contents);
221
- } catch {}
221
+ } catch {
222
+ //ignore
223
+ }
222
224
  if (scanstr.includes("<!--SCPT:")) {
223
225
  const start = scanstr.indexOf("<!--SCPT:");
224
226
  const end = scanstr.indexOf("-->", start);
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
@@ -5,7 +5,7 @@ const { getState } = require("@saltcorn/data/db/state");
5
5
  const fileUpload = require("express-fileupload");
6
6
  const { v4: uuidv4 } = require("uuid");
7
7
  const contentDisposition = require("content-disposition");
8
-
8
+ const fs = require("fs");
9
9
  function createS3Client() {
10
10
  return new aws.S3({
11
11
  secretAccessKey: getState().getConfig("storage_s3_access_secret"),
@@ -52,19 +52,18 @@ module.exports = {
52
52
  createParentPath: true,
53
53
  tempFileDir: "/tmp/",
54
54
  // set to true - if you want to have debug
55
- debug: getState().getConfig("file_upload_debug",false),
55
+ debug: getState().getConfig("file_upload_debug", false),
56
56
  //uriDecodeFileNames: true,
57
57
  //safeFileNames: true,
58
- defCharset: 'utf8',
59
- defParamCharset: 'utf8',
58
+ defCharset: "utf8",
59
+ defParamCharset: "utf8",
60
60
  // 0 - means no upload limit check
61
61
  limits: {
62
62
  fileSize: fileSizeLimit,
63
63
  },
64
64
  abortOnLimit: fileSizeLimit !== 0,
65
65
  // 0 - means no upload limit check
66
- uploadTimeout: getState().getConfig("file_upload_timeout",0),
67
-
66
+ uploadTimeout: getState().getConfig("file_upload_timeout", 0),
68
67
  })(req, res, next);
69
68
  }
70
69
  },
@@ -146,7 +145,7 @@ module.exports = {
146
145
  // Forward the object
147
146
  s3.getObject(params)
148
147
  .on("httpHeaders", function (statusCode, headers) {
149
- if (!!download)
148
+ if (download)
150
149
  res.set("Content-Disposition", contentDisposition(file.filename));
151
150
  res.set("Content-Length", headers["content-length"]);
152
151
  this.response.httpResponse.createUnbufferedStream().pipe(res);
package/serve.js CHANGED
@@ -30,7 +30,11 @@ const { getConfig } = require("@saltcorn/data/models/config");
30
30
  const { migrate } = require("@saltcorn/data/migrate");
31
31
  const socketio = require("socket.io");
32
32
  const { createAdapter, setupPrimary } = require("@socket.io/cluster-adapter");
33
- const { setTenant, getSessionStore, get_tenant_from_req } = require("./routes/utils");
33
+ const {
34
+ setTenant,
35
+ getSessionStore,
36
+ get_tenant_from_req,
37
+ } = require("./routes/utils");
34
38
  const passport = require("passport");
35
39
  const { authenticate } = require("passport");
36
40
  const View = require("@saltcorn/data/models/view");
@@ -106,8 +110,8 @@ const workerDispatchMsg = ({ tenant, ...msg }) => {
106
110
  });
107
111
  }
108
112
  if (!getState()) {
109
- console.error("no State for tenant", tenant)
110
- return
113
+ console.error("no State for tenant", tenant);
114
+ return;
111
115
  }
112
116
  if (msg.refresh) getState()[`refresh_${msg.refresh}`](true);
113
117
  if (msg.createTenant) {
@@ -142,33 +146,33 @@ const workerDispatchMsg = ({ tenant, ...msg }) => {
142
146
  */
143
147
  const onMessageFromWorker =
144
148
  (masterState, { port, watchReaper, disableScheduler, pid }) =>
145
- (msg) => {
146
- //console.log("worker msg", typeof msg, msg);
147
- if (msg === "Start" && !masterState.started) {
148
- masterState.started = true;
149
- runScheduler({
150
- port,
151
- watchReaper,
152
- disableScheduler,
153
- eachTenant,
154
- auto_backup_now,
155
- take_snapshot,
156
- });
157
- require("./systemd")({ port });
158
- return true;
159
- } else if (msg === "RestartServer") {
160
- process.exit(0);
161
- return true;
162
- } else if (msg.tenant || msg.createTenant) {
163
- ///ie from saltcorn
164
- //broadcast
165
- Object.entries(cluster.workers).forEach(([wpid, w]) => {
166
- if (wpid !== pid) w.send(msg);
167
- });
168
- workerDispatchMsg(msg); //also master
169
- return true;
170
- }
171
- };
149
+ (msg) => {
150
+ //console.log("worker msg", typeof msg, msg);
151
+ if (msg === "Start" && !masterState.started) {
152
+ masterState.started = true;
153
+ runScheduler({
154
+ port,
155
+ watchReaper,
156
+ disableScheduler,
157
+ eachTenant,
158
+ auto_backup_now,
159
+ take_snapshot,
160
+ });
161
+ require("./systemd")({ port });
162
+ return true;
163
+ } else if (msg === "RestartServer") {
164
+ process.exit(0);
165
+ return true;
166
+ } else if (msg.tenant || msg.createTenant) {
167
+ ///ie from saltcorn
168
+ //broadcast
169
+ Object.entries(cluster.workers).forEach(([wpid, w]) => {
170
+ if (wpid !== pid) w.send(msg);
171
+ });
172
+ workerDispatchMsg(msg); //also master
173
+ return true;
174
+ }
175
+ };
172
176
 
173
177
  module.exports =
174
178
  /**
@@ -381,7 +385,7 @@ const setupSocket = (...servers) => {
381
385
  } catch (err) {
382
386
  getState().log(1, `Socket join_room error: ${err.stack}`);
383
387
  }
384
- }
388
+ };
385
389
  if (ten && ten !== "public") db.runWithTenant(ten, f);
386
390
  else f();
387
391
  });