@saltcorn/server 1.1.1 → 1.1.2-beta.1

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.
@@ -477,9 +477,9 @@ router.post(
477
477
  "/edit-properties",
478
478
  isAdminOrHasConfigMinRole("min_role_edit_pages"),
479
479
  error_catcher(async (req, res) => {
480
- const form = await pagePropertiesForm(req, !req.body.id);
480
+ const form = await pagePropertiesForm(req, !(req.body || {}).id);
481
481
  form.hidden("id");
482
- form.validate(req.body);
482
+ form.validate(req.body || {});
483
483
  if (form.hasErrors) {
484
484
  res.sendWrap(
485
485
  req.__(`Page attributes`),
@@ -688,9 +688,9 @@ router.post(
688
688
  if (!page) {
689
689
  req.flash("error", req.__(`Page %s not found`, pagename));
690
690
  res.redirect(redirectTarget);
691
- } else if (req.body.layout) {
691
+ } else if ((req.body || {}).layout) {
692
692
  await Page.update(page.id, {
693
- layout: decodeURIComponent(req.body.layout),
693
+ layout: decodeURIComponent((req.body || {}).layout),
694
694
  });
695
695
  Trigger.emitEvent("AppChange", `Page ${page.name}`, req.user, {
696
696
  entity_type: "Page",
@@ -698,12 +698,12 @@ router.post(
698
698
  });
699
699
  req.flash("success", req.__(`Page %s saved`, pagename));
700
700
  res.redirect(redirectTarget);
701
- } else if (req.body.code) {
701
+ } else if ((req.body || {}).code) {
702
702
  try {
703
703
  if (!page.html_file) throw new Error(req.__("File not found"));
704
704
  const file = await File.findOne(page.html_file);
705
705
  if (!file) throw new Error(req.__("File not found"));
706
- await fsp.writeFile(file.location, req.body.code);
706
+ await fsp.writeFile(file.location, (req.body || {}).code);
707
707
  Trigger.emitEvent("AppChange", `Page ${page.name}`, req.user, {
708
708
  entity_type: "Page",
709
709
  entity_name: page.name,
@@ -722,7 +722,7 @@ router.post(
722
722
  else res.json({ error: error.message });
723
723
  }
724
724
  } else {
725
- getState().log(2, `POST /edit/${pagename}: '${req.body}'`);
725
+ getState().log(2, `POST /edit/${pagename}: '${req.body || {}}'`);
726
726
  req.flash("error", req.__(`Error processing page`));
727
727
  res.redirect(redirectTarget);
728
728
  }
@@ -742,8 +742,8 @@ router.post(
742
742
  error_catcher(async (req, res) => {
743
743
  const { id } = req.params;
744
744
 
745
- if (id && req.body.layout) {
746
- await Page.update(+id, { layout: req.body.layout });
745
+ if (id && (req.body || {}).layout) {
746
+ await Page.update(+id, { layout: (req.body || {}).layout });
747
747
  const page = await Page.findOne({ id });
748
748
  Trigger.emitEvent("AppChange", `Page ${page.name}`, req.user, {
749
749
  entity_type: "Page",
@@ -794,7 +794,7 @@ router.post(
794
794
  const pageGroups = await PageGroup.find({}, { orderBy: "name" });
795
795
  const roles = await User.get_roles();
796
796
  const form = getRootPageForm(pages, pageGroups, roles, req);
797
- const valres = form.validate(req.body);
797
+ const valres = form.validate(req.body || {});
798
798
  if (valres.success) {
799
799
  const home_page_by_role =
800
800
  getState().getConfigCopy("home_page_by_role", {}) || {};
package/routes/plugins.js CHANGED
@@ -839,7 +839,7 @@ router.post(
839
839
  flow.action = `/plugins/configure/${encodeURIComponent(plugin.name)}`;
840
840
  flow.autoSave = true;
841
841
  flow.saveURL = `/plugins/saveconfig/${encodeURIComponent(plugin.name)}`;
842
- const wfres = await flow.run(req.body);
842
+ const wfres = await flow.run(req.body || {});
843
843
  if (wfres.renderForm) {
844
844
  if (module.layout) {
845
845
  wfres.renderForm.additionalButtons = [
@@ -893,7 +893,7 @@ router.post(
893
893
  module = getState().plugins[getState().plugin_module_names[plugin.name]];
894
894
  }
895
895
  const flow = module.configuration_workflow();
896
- const step = await flow.singleStepForm(req.body, req);
896
+ const step = await flow.singleStepForm(req.body || {}, req);
897
897
  if (step?.renderForm) {
898
898
  if (step.renderForm.hasErrors || step.savingErrors)
899
899
  res.status(400).send(step.savingErrors || "Error");
@@ -1003,7 +1003,7 @@ router.post(
1003
1003
  ...(plugin.configuration || {}),
1004
1004
  ...(user._attributes?.layout?.config || {}),
1005
1005
  });
1006
- const valResult = form.validate(req.body);
1006
+ const valResult = form.validate(req.body || {});
1007
1007
  if (form.hasErrors) {
1008
1008
  req.flash("warning", req.__("An error occurred"));
1009
1009
  return res.sendWrap(
@@ -1056,7 +1056,7 @@ router.post(
1056
1056
  ...(plugin.configuration || {}),
1057
1057
  ...(user._attributes?.layout?.config || {}),
1058
1058
  });
1059
- const valResult = form.validate(req.body);
1059
+ const valResult = form.validate(req.body || {});
1060
1060
  if (form.hasErrors) {
1061
1061
  return res.status(400).json({ error: req.__("An error occured") });
1062
1062
  }
@@ -1154,10 +1154,10 @@ router.get(
1154
1154
  * @function
1155
1155
  */
1156
1156
  router.get(
1157
- "/public/:plugin/*",
1157
+ "/public/:plugin/*filepath",
1158
1158
  error_catcher(async (req, res) => {
1159
1159
  const { plugin } = req.params;
1160
- const filepath = req.params[0];
1160
+ const filepath = path.join(...req.params.filepath);
1161
1161
  const hasVersion = plugin.includes("@");
1162
1162
  const location =
1163
1163
  getState().plugin_locations[hasVersion ? plugin.split("@")[0] : plugin];
@@ -1167,7 +1167,10 @@ router.get(
1167
1167
  .replace(/^(\.\.(\/|\\|$))+/, "");
1168
1168
  const fullpath = path.join(location, "public", safeFile);
1169
1169
  if (fs.existsSync(fullpath))
1170
- res.sendFile(fullpath, { maxAge: hasVersion ? "100d" : "1d" });
1170
+ res.sendFile(fullpath, {
1171
+ maxAge: hasVersion ? "100d" : "1d",
1172
+ dotfiles: "allow",
1173
+ });
1171
1174
  else {
1172
1175
  getState().log(6, `Plugin serve public: file not found ${fullpath}`);
1173
1176
  res.status(404).send(req.__("Not found"));
@@ -1428,7 +1431,7 @@ router.post(
1428
1431
  "/",
1429
1432
  isAdmin,
1430
1433
  error_catcher(async (req, res) => {
1431
- const plugin = new Plugin(req.body);
1434
+ const plugin = new Plugin(req.body || {});
1432
1435
  const schema = db.getTenantSchema();
1433
1436
  const tenants_install_git = getRootState().getConfig(
1434
1437
  "tenants_install_git",
@@ -1505,7 +1508,7 @@ router.post(
1505
1508
  isAdmin,
1506
1509
  error_catcher(async (req, res) => {
1507
1510
  const { name } = req.params;
1508
- const { version } = req.body;
1511
+ const { version } = req.body || {};
1509
1512
  const tenants_unsafe_plugins = getRootState().getConfig(
1510
1513
  "tenants_unsafe_plugins",
1511
1514
  false
@@ -340,7 +340,7 @@ router.post(
340
340
  const { etype, ename, q } = req.query;
341
341
  const qlink = q ? `&q=${encodeURIComponent(q)}` : "";
342
342
 
343
- const entVal = JSON.parse(req.body.regval);
343
+ const entVal = JSON.parse((req.body || {}).regval);
344
344
  let pack = {
345
345
  plugins: [],
346
346
  tables: [],
package/routes/search.js CHANGED
@@ -136,7 +136,7 @@ router.post(
136
136
  const views = await View.find({}, { orderBy: "name" });
137
137
  const tables = await Table.find();
138
138
  const form = searchConfigForm(tables, views, req);
139
- const result = form.validate(req.body);
139
+ const result = form.validate(req.body || {});
140
140
 
141
141
  if (result.success) {
142
142
  const dbversion = await db.getVersion(true);
package/routes/sync.js CHANGED
@@ -118,7 +118,7 @@ router.post(
118
118
  "/load_changes",
119
119
  error_catcher(async (req, res) => {
120
120
  const result = {};
121
- const { syncInfos, loadUntil } = req.body;
121
+ const { syncInfos, loadUntil } = req.body || {};
122
122
  if (!loadUntil) {
123
123
  getState().log(2, `POST /load_changes: loadUntil is missing`);
124
124
  return res.status(400).json({ error: "loadUntil is missing" });
@@ -202,7 +202,7 @@ const getDelRows = async (tblName, syncFrom, syncUntil, client) => {
202
202
  router.post(
203
203
  "/deletes",
204
204
  error_catcher(async (req, res) => {
205
- const { syncInfos, syncTimestamp } = req.body;
205
+ const { syncInfos, syncTimestamp } = req.body || {};
206
206
  const client = await db.getClient();
207
207
  try {
208
208
  await client.query(`BEGIN`);
@@ -238,7 +238,7 @@ router.post(
238
238
  router.post(
239
239
  "/offline_changes",
240
240
  error_catcher(async (req, res) => {
241
- const { changes, syncTimestamp } = req.body;
241
+ const { changes, syncTimestamp } = req.body || {};
242
242
  const rootFolder = await File.rootFolder();
243
243
  try {
244
244
  const syncDirName = `${syncTimestamp}_${req.user?.email || "public"}`;
@@ -334,7 +334,7 @@ router.get(
334
334
  router.post(
335
335
  "/clean_sync_dir",
336
336
  error_catcher(async (req, res) => {
337
- const { dir_name } = req.body;
337
+ const { dir_name } = req.body || {};
338
338
  try {
339
339
  const rootFolder = await File.rootFolder();
340
340
  const syncDir = File.normalise_in_base(
package/routes/tables.js CHANGED
@@ -51,7 +51,7 @@ const {
51
51
  pre,
52
52
  button,
53
53
  } = require("@saltcorn/markup/tags");
54
- const stringify = require("csv-stringify");
54
+ const { stringify } = require("csv-stringify");
55
55
  const TableConstraint = require("@saltcorn/data/models/table_constraints");
56
56
  const fs = require("fs").promises;
57
57
  const {
@@ -359,7 +359,7 @@ router.post(
359
359
  error_catcher(async (req, res) => {
360
360
  const tbls = await discoverable_tables();
361
361
  const form = discoverForm(tbls, req);
362
- form.validate(req.body);
362
+ form.validate(req.body || {});
363
363
  const tableNames = tbls
364
364
  .filter((t) => form.values[t.table_name])
365
365
  .map((t) => t.table_name);
@@ -437,8 +437,8 @@ router.post(
437
437
  setTenant,
438
438
  isAdminOrHasConfigMinRole("min_role_edit_tables"),
439
439
  error_catcher(async (req, res) => {
440
- if (req.body.name && req.files && req.files.file) {
441
- const name = req.body.name;
440
+ if ((req.body || {}).name && req.files && req.files.file) {
441
+ const name = (req.body || {}).name;
442
442
  const alltables = await Table.find({});
443
443
  const existing_tables = [
444
444
  "users",
@@ -645,8 +645,8 @@ router.get(
645
645
  div(
646
646
  {
647
647
  id: "erd-wrapper",
648
- style: "height: calc(100vh - 250px);",
649
- class: "overflow-scroll position-relative",
648
+ style:
649
+ "height: calc(100vh - 250px); overflow: hidden !important;",
650
650
  },
651
651
  screenshotPanel(),
652
652
  pre(
@@ -1169,7 +1169,7 @@ router.post(
1169
1169
  "/",
1170
1170
  isAdminOrHasConfigMinRole("min_role_edit_tables"),
1171
1171
  error_catcher(async (req, res) => {
1172
- const v = req.body;
1172
+ const v = req.body || {};
1173
1173
  if (typeof v.id === "undefined" && typeof v.external === "undefined") {
1174
1174
  // insert
1175
1175
  v.name = v.name.trim();
@@ -1634,21 +1634,14 @@ const constraintForm = (req, table, fields, type) => {
1634
1634
  case "Index":
1635
1635
  const fieldopts = fields.map((f) => ({ label: f.label, name: f.name }));
1636
1636
  const hasIncludeFts = fields.filter((f) => f.attributes?.include_fts);
1637
- if (!db.isSQLite && !hasIncludeFts.length)
1637
+ if (!db.isSQLite)
1638
1638
  fieldopts.push({ label: "Full-text search", name: "_fts" });
1639
1639
  return new Form({
1640
1640
  action: `/table/add-constraint/${table.id}/${type}`,
1641
- blurb:
1642
- req.__(
1643
- "Choose the field to be indexed. This make searching the table faster."
1644
- ) +
1645
- " " +
1646
- (hasIncludeFts.length
1647
- ? req.__(
1648
- `Full-text search index is not available as the table contains Key fields (%s) with the "Include in full-text search" option enabled. Disable this before creating a Full-text search index`,
1649
- hasIncludeFts.map((f) => f.name).join(",")
1650
- )
1651
- : ""),
1641
+ blurb: req.__(
1642
+ "Choose the field to be indexed. This make searching the table faster."
1643
+ ),
1644
+
1652
1645
  fields: [
1653
1646
  {
1654
1647
  type: "String",
@@ -1657,6 +1650,11 @@ const constraintForm = (req, table, fields, type) => {
1657
1650
  required: true,
1658
1651
  attributes: {
1659
1652
  options: fieldopts,
1653
+ explainers: hasIncludeFts
1654
+ ? {
1655
+ _fts: "Full text search index is not compatible with Key fields with the 'Include in Full text search' option. A new field will be created for your search context",
1656
+ }
1657
+ : {},
1660
1658
  },
1661
1659
  },
1662
1660
  ],
@@ -1729,7 +1727,7 @@ router.post(
1729
1727
  }
1730
1728
  const fields = table.getFields();
1731
1729
  const form = constraintForm(req, table, fields, type);
1732
- form.validate(req.body);
1730
+ form.validate(req.body || {});
1733
1731
  if (form.hasErrors) req.flash("error", req.__("An error occurred"));
1734
1732
  else {
1735
1733
  let configuration = {};
@@ -1820,7 +1818,7 @@ router.post(
1820
1818
  const table = Table.findOne({ id });
1821
1819
  const form = renameForm(table.id, req);
1822
1820
 
1823
- form.validate(req.body);
1821
+ form.validate(req.body || {});
1824
1822
  if (form.hasErrors) req.flash("error", req.__("An error occurred"));
1825
1823
  else {
1826
1824
  await table.rename(form.values.name);
@@ -2188,7 +2186,7 @@ router.post(
2188
2186
  return;
2189
2187
  }
2190
2188
  const workflow = get_provider_workflow(table, req);
2191
- const wfres = await workflow.run(req.body, req);
2189
+ const wfres = await workflow.run(req.body || {}, req);
2192
2190
  respondWorkflow(table, workflow, wfres, req, res);
2193
2191
  })
2194
2192
  );
@@ -161,7 +161,7 @@ router.post(
161
161
  isAdmin,
162
162
  error_catcher(async (req, res) => {
163
163
  const { entry_type, tag_id } = req.params;
164
- const { ids } = req.body;
164
+ const { ids } = req.body || {};
165
165
  if (!ids) {
166
166
  req.flash("error", req.__("Please select at least one item"));
167
167
  return res.redirect(`/tag-entries/add/${entry_type}/${tag_id}`);
@@ -218,7 +218,7 @@ router.post(
218
218
  isAdmin,
219
219
  error_catcher(async (req, res) => {
220
220
  let { entry_type, object_id } = req.params;
221
- let { tag_ids } = req.body;
221
+ let { tag_ids } = req.body || {};
222
222
  object_id = parseInt(object_id);
223
223
  tag_ids = tag_ids.map((id) => parseInt(id));
224
224
  const tags = (await Tag.find()).filter((tag) => tag_ids.includes(tag.id));
package/routes/tags.js CHANGED
@@ -315,7 +315,7 @@ router.post(
315
315
  "/",
316
316
  isAdmin,
317
317
  error_catcher(async (req, res) => {
318
- const { name } = req.body;
318
+ const { name } = req.body || {};
319
319
  const tag = await Tag.create({ name });
320
320
  req.flash("success", req.__(`Tag %s created`, name));
321
321
  res.redirect(`/tag/${tag.id}?show_list=tables`);
package/routes/tenant.js CHANGED
@@ -265,7 +265,7 @@ router.post(
265
265
  const base_url = get_cfg_tenant_base_url(req);
266
266
  const form = tenant_form(req, base_url);
267
267
  // validate ui form
268
- const valres = form.validate(req.body);
268
+ const valres = form.validate(req.body || {});
269
269
  if (valres.errors)
270
270
  res.sendWrap(
271
271
  req.__("Create application"),
@@ -548,7 +548,7 @@ router.post(
548
548
  isAdmin,
549
549
  error_catcher(async (req, res) => {
550
550
  const form = await tenant_settings_form(req);
551
- form.validate(req.body);
551
+ form.validate(req.body || {});
552
552
  if (form.hasErrors) {
553
553
  send_infoarch_page({
554
554
  res,
@@ -872,11 +872,11 @@ router.post(
872
872
  return;
873
873
  }
874
874
  const { subdomain } = req.params;
875
- const { base_url } = req.body;
875
+ const { base_url } = req.body || {};
876
876
  const saneDomain = domain_sanitize(subdomain);
877
877
 
878
878
  // save description
879
- const { description } = req.body;
879
+ const { description } = req.body || {};
880
880
  await Tenant.update(saneDomain, { description: description });
881
881
 
882
882
  await db.runWithTenant(saneDomain, async () => {
package/routes/utils.js CHANGED
@@ -446,7 +446,7 @@ const admin_config_route = ({
446
446
  isAdmin,
447
447
  error_catcher(async (req, res) => {
448
448
  const form = await getTheForm(req);
449
- form.validate(req.body);
449
+ form.validate(req.body || {});
450
450
  if (form.hasErrors) {
451
451
  response(form, req, res);
452
452
  } else {
@@ -493,7 +493,7 @@ const sendHtmlFile = async (req, res, file) => {
493
493
  path.dirname(fullPath)
494
494
  );
495
495
  if (scFile && role <= scFile.min_role_read) {
496
- res.sendFile(fullPath);
496
+ res.sendFile(fullPath, { dotfiles: "allow" });
497
497
  } else {
498
498
  return res
499
499
  .status(404)
@@ -517,7 +517,7 @@ const sendHtmlFile = async (req, res, file) => {
517
517
  */
518
518
  const setRole = async (req, res, model) => {
519
519
  const { id } = req.params;
520
- const role = req.body.role;
520
+ const role = (req.body || {}).role;
521
521
  await model.update(+id, { min_role: role });
522
522
  const page = model.findOne({ id });
523
523
  const roles = await User.get_roles();
package/routes/view.js CHANGED
@@ -39,7 +39,7 @@ module.exports = router;
39
39
  * @function
40
40
  */
41
41
  router.get(
42
- ["/:viewname", "/:viewname/*"],
42
+ ["/:viewname", "/:viewname/*slug"],
43
43
  error_catcher(async (req, res) => {
44
44
  const { viewname } = req.params;
45
45
  const query = { ...req.query };
@@ -60,7 +60,7 @@ router.get(
60
60
  }
61
61
  const tic = new Date();
62
62
 
63
- view.rewrite_query_from_slug(query, req.params);
63
+ view.rewrite_query_from_slug(query, req.params.slug);
64
64
  if (
65
65
  role > view.min_role &&
66
66
  !(await view.authorise_get({ query, req, ...view }))
@@ -231,7 +231,7 @@ router.post(
231
231
 
232
232
  res.redirect("/");
233
233
  } else {
234
- await view.runRoute(route, req.body, res, { res, req });
234
+ await view.runRoute(route, req.body || {}, res, { res, req });
235
235
  }
236
236
  })
237
237
  );
@@ -243,7 +243,7 @@ router.post(
243
243
  * @function
244
244
  */
245
245
  router.post(
246
- ["/:viewname", "/:viewname/*"],
246
+ ["/:viewname", "/:viewname/*slug"],
247
247
  setTenant,
248
248
  error_catcher(async (req, res) => {
249
249
  const { viewname } = req.params;
@@ -263,11 +263,11 @@ router.post(
263
263
  res.redirect("/");
264
264
  return;
265
265
  }
266
- view.rewrite_query_from_slug(query, req.params);
266
+ view.rewrite_query_from_slug(query, req.params.slug);
267
267
 
268
268
  if (
269
269
  role > view.min_role &&
270
- !(await view.authorise_post({ body: req.body, req, ...view }))
270
+ !(await view.authorise_post({ body: req.body || {}, req, ...view }))
271
271
  ) {
272
272
  req.flash("danger", req.__("Not authorized"));
273
273
  state.log(2, `View ${viewname} POST not authorized`);
@@ -280,7 +280,7 @@ router.post(
280
280
  } does not supply a POST handler`
281
281
  );
282
282
  } else {
283
- await view.runPost(query, req.body, { res, req });
283
+ await view.runPost(query, req.body || {}, { res, req });
284
284
  }
285
285
  })
286
286
  );
@@ -492,7 +492,7 @@ router.post(
492
492
  const roles = await User.get_roles();
493
493
  const pages = await Page.find();
494
494
  const form = await viewForm(req, tableOptions, roles, pages);
495
- const result = form.validate(req.body);
495
+ const result = form.validate(req.body || {});
496
496
  const sendForm = (form) => {
497
497
  res.sendWrap(req.__(`Edit view`), {
498
498
  above: [
@@ -521,7 +521,7 @@ router.post(
521
521
  } else {
522
522
  const existing_view = await View.findOne({ name: result.success.name });
523
523
  if (existing_view)
524
- if (+req.body.id !== existing_view.id) {
524
+ if (+(req.body || {}).id !== existing_view.id) {
525
525
  // may be need !== but doesnt work
526
526
  form.errors.name = req.__("A view with this name already exists");
527
527
  form.hasErrors = true;
@@ -544,8 +544,8 @@ router.post(
544
544
  }
545
545
  //const table = Table.findOne({ name: v.table_name });
546
546
  delete v.table_name;
547
- if (req.body.id) {
548
- await View.update(v, +req.body.id);
547
+ if ((req.body || {}).id) {
548
+ await View.update(v, +(req.body || {}).id);
549
549
  } else {
550
550
  const vt = getState().viewtemplates[v.viewtemplate];
551
551
  if (vt.initial_config) v.configuration = await vt.initial_config(v);
@@ -744,7 +744,7 @@ router.post(
744
744
  entity_name: view.name,
745
745
  });
746
746
  };
747
- const wfres = await configFlow.run(req.body, req);
747
+ const wfres = await configFlow.run(req.body || {}, req);
748
748
 
749
749
  let table;
750
750
  if (view.table_id) table = Table.findOne({ id: view.table_id });
@@ -852,9 +852,9 @@ router.post(
852
852
  error_catcher(async (req, res) => {
853
853
  const { id } = req.params;
854
854
 
855
- if (id && req.body) {
855
+ if (id && (req.body || {})) {
856
856
  const exview = await View.findOne({ id });
857
- let newcfg = { ...exview.configuration, ...req.body };
857
+ let newcfg = { ...exview.configuration, ...(req.body || {}) };
858
858
  await View.update({ configuration: newcfg }, +id);
859
859
  Trigger.emitEvent("AppChange", `View ${exview.name}`, req.user, {
860
860
  entity_type: "View",
@@ -880,11 +880,11 @@ router.post(
880
880
  error_catcher(async (req, res) => {
881
881
  const { viewname } = req.params;
882
882
 
883
- if (viewname && req.body) {
883
+ if (viewname && (req.body || {})) {
884
884
  const view = await View.findOne({ name: viewname });
885
885
  req.staticFieldViewConfig = true;
886
886
  const configFlow = await view.get_config_flow(req);
887
- const step = await configFlow.singleStepForm(req.body, req);
887
+ const step = await configFlow.singleStepForm(req.body || {}, req);
888
888
  if (step?.renderForm) {
889
889
  if (!step.renderForm.hasErrors) {
890
890
  let newcfg;
@@ -926,7 +926,7 @@ router.post(
926
926
  isAdminOrHasConfigMinRole("min_role_edit_views"),
927
927
  error_catcher(async (req, res) => {
928
928
  const { id } = req.params;
929
- const role = req.body.role;
929
+ const role = (req.body || {}).role;
930
930
  await View.update({ min_role: role }, +id);
931
931
  const view = await View.findOne({ id });
932
932
  Trigger.emitEvent("AppChange", `View ${view.name}`, req.user, {
@@ -955,7 +955,7 @@ router.post(
955
955
  "/test/inserter",
956
956
  isAdminOrHasConfigMinRole("min_role_edit_views"),
957
957
  error_catcher(async (req, res) => {
958
- const view = await View.create(req.body);
958
+ const view = await View.create(req.body || {});
959
959
  res.json({ view });
960
960
  })
961
961
  );
package/s3storage.js CHANGED
@@ -159,7 +159,7 @@ module.exports = {
159
159
  .send();
160
160
  } else {
161
161
  // Use legacy file download
162
- res.download(file.location, file.filename);
162
+ res.download(file.location, file.filename, { dotfiles: "allow" });
163
163
  }
164
164
  },
165
165
 
@@ -358,9 +358,19 @@ describe("visible_entries test", () => {
358
358
  .send(`role=${role}`)
359
359
  .expect(toRedirect("/files?dir=_sc_test_subfolder_one"));
360
360
  };
361
+ const setDirRole = async (role, entry) => {
362
+ const app = await getApp({ disableCsrf: true });
363
+ const adminCookie = await getAdminLoginCookie();
364
+ await request(app)
365
+ .post(`/files/setrole/${entry}`)
366
+ .set("Cookie", adminCookie)
367
+ .send(`role=${role}`)
368
+ .expect(toRedirect("/files?dir=."));
369
+ };
361
370
 
362
371
  it("shows allowed files", async () => {
363
372
  await setRole(100, path.join("_sc_test_subfolder_one", "foo_image.png"));
373
+ await setDirRole(100, "_sc_test_subfolder_one");
364
374
 
365
375
  const app = await getApp({ disableCsrf: true });
366
376
  const staffCookie = await getStaffLoginCookie();
@@ -91,9 +91,7 @@ describe("Plugin Endpoints", () => {
91
91
  .get("/plugins/public/any-bootstrap-theme/test.txt")
92
92
  .expect(toInclude("testfilecontents"));
93
93
  await request(app)
94
- .get(
95
- "/plugins/public/sbadmin2@9.9.9/sb-admin-2.min.css"
96
- )
94
+ .get("/plugins/public/sbadmin2@9.9.9/sb-admin-2.min.css")
97
95
  .expect(toInclude("Start Bootstrap"));
98
96
 
99
97
  await request(app)
@@ -101,9 +99,7 @@ describe("Plugin Endpoints", () => {
101
99
  .set("Cookie", loginCookie)
102
100
  .expect(toRedirect("/plugins"));
103
101
  await request(app)
104
- .get(
105
- "/plugins/public/sbadmin2@9.9.9/sb-admin-2.min.css"
106
- )
102
+ .get("/plugins/public/sbadmin2@9.9.9/sb-admin-2.min.css")
107
103
  .expect(toInclude("Start Bootstrap"));
108
104
  });
109
105
  it("should install named without config", async () => {