@saltcorn/server 0.8.2-beta.0 → 0.8.3-alpha.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/app.js CHANGED
@@ -184,7 +184,7 @@ const getApp = async (opts = {}) => {
184
184
  new CustomStrategy((req, done) => {
185
185
  loginAttempt();
186
186
  async function loginAttempt() {
187
- const { remember, _csrf, ...userobj } = req.body;
187
+ const { remember, _csrf, dest, ...userobj } = req.body;
188
188
  if (!is.objVals(is.str).check(userobj))
189
189
  return done(
190
190
  null,
package/auth/routes.js CHANGED
@@ -93,7 +93,7 @@ const loginForm = (req, isCreating) => {
93
93
  // TBD unresolved usernameLabel
94
94
  .map((auth) => `${auth.usernameLabel} for ${auth.label}`)
95
95
  .join(", ");
96
- return new Form({
96
+ const form = new Form({
97
97
  class: "login",
98
98
  fields: [
99
99
  new Field({
@@ -119,6 +119,12 @@ const loginForm = (req, isCreating) => {
119
119
  action: "/auth/login",
120
120
  submitLabel: req.__("Login"),
121
121
  });
122
+ const { dest } = req.query;
123
+ if (dest) {
124
+ form.hidden("dest");
125
+ form.values.dest = encodeURIComponent(dest);
126
+ }
127
+ return form;
122
128
  };
123
129
 
124
130
  /**
@@ -1049,7 +1055,6 @@ router.post(
1049
1055
  res.redirect("/auth/twofa/login/totp");
1050
1056
  return;
1051
1057
  }
1052
-
1053
1058
  if (req.session.cookie)
1054
1059
  if (req.body.remember) {
1055
1060
  const setDur = +getState().getConfig("cookie_duration_remember", 0);
@@ -1070,6 +1075,8 @@ router.post(
1070
1075
  }
1071
1076
  if (getState().get2FApolicy(req.user) === "Mandatory") {
1072
1077
  res.redirect("/auth/twofa/setup/totp");
1078
+ } else if (req.body.dest) {
1079
+ res.redirect(decodeURIComponent(req.body.dest));
1073
1080
  } else res.redirect("/");
1074
1081
  })
1075
1082
  );
package/auth/testhelp.js CHANGED
@@ -131,7 +131,10 @@ const itShouldRedirectUnauthToLogin = (path, dest) => {
131
131
  const res = await request(app)
132
132
  .get(path)
133
133
  .expect(302)
134
- .expect("Location", dest || "/auth/login");
134
+ .expect(
135
+ "Location",
136
+ dest || `/auth/login?dest=${encodeURIComponent(path)}`
137
+ );
135
138
 
136
139
  expect(res.statusCode).toEqual(302);
137
140
  });
package/locales/en.json CHANGED
@@ -1043,7 +1043,6 @@
1043
1043
  "Rights settings": "Rights settings",
1044
1044
  "Database name": "Database name",
1045
1045
  "Database schema": "Database schema",
1046
- "<p>You have views with a role to access lower than the table role to read, \n with no table ownership. In the next version of Saltcorn, this may cause a\n denial of access. Users will need to have table read access to any data displayed.</p> \n Views potentially affected: %s": "<p>You have views with a role to access lower than the table role to read, \n with no table ownership. In the next version of Saltcorn, this may cause a\n denial of access. Users will need to have table read access to any data displayed.</p> \n Views potentially affected: %s",
1047
1046
  "If the parent row is deleted, do this to the child rows.": "If the parent row is deleted, do this to the child rows.",
1048
1047
  "On delete": "On delete",
1049
1048
  "Permissions": "Permissions",
@@ -1082,5 +1081,9 @@
1082
1081
  "Cache-control max-age for public views and pages. 0 to disable": "Cache-control max-age for public views and pages. 0 to disable",
1083
1082
  "Files accept filter": "Files accept filter",
1084
1083
  "User group": "User group",
1085
- "Add relations to this table in dropdown options for ownership field": "Add relations to this table in dropdown options for ownership field"
1084
+ "Add relations to this table in dropdown options for ownership field": "Add relations to this table in dropdown options for ownership field",
1085
+ "You have views with a role to access lower than the table role to read, with no table ownership. This may cause a denial of access. Users need to have table read access to any data displayed.": "You have views with a role to access lower than the table role to read, with no table ownership. This may cause a denial of access. Users need to have table read access to any data displayed.",
1086
+ "Views potentially affected": "Views potentially affected",
1087
+ "Empty view": "Empty view",
1088
+ "A view that will be shown only if there are no tables rows to show": "A view that will be shown only if there are no tables rows to show"
1086
1089
  }
package/package.json CHANGED
@@ -1,18 +1,18 @@
1
1
  {
2
2
  "name": "@saltcorn/server",
3
- "version": "0.8.2-beta.0",
3
+ "version": "0.8.3-alpha.0",
4
4
  "description": "Server app for Saltcorn, open-source no-code platform",
5
5
  "homepage": "https://saltcorn.com",
6
6
  "main": "index.js",
7
7
  "license": "MIT",
8
8
  "dependencies": {
9
- "@saltcorn/base-plugin": "0.8.2-beta.0",
10
- "@saltcorn/builder": "0.8.2-beta.0",
11
- "@saltcorn/data": "0.8.2-beta.0",
12
- "@saltcorn/admin-models": "0.8.2-beta.0",
13
- "@saltcorn/filemanager": "0.8.2-beta.0",
14
- "@saltcorn/markup": "0.8.2-beta.0",
15
- "@saltcorn/sbadmin2": "0.8.2-beta.0",
9
+ "@saltcorn/base-plugin": "0.8.3-alpha.0",
10
+ "@saltcorn/builder": "0.8.3-alpha.0",
11
+ "@saltcorn/data": "0.8.3-alpha.0",
12
+ "@saltcorn/admin-models": "0.8.3-alpha.0",
13
+ "@saltcorn/filemanager": "0.8.3-alpha.0",
14
+ "@saltcorn/markup": "0.8.3-alpha.0",
15
+ "@saltcorn/sbadmin2": "0.8.3-alpha.0",
16
16
  "@socket.io/cluster-adapter": "^0.1.0",
17
17
  "@socket.io/sticky": "^1.0.1",
18
18
  "aws-sdk": "^2.1037.0",
@@ -33,7 +33,7 @@
33
33
  "greenlock-express": "^4.0.3",
34
34
  "helmet": "^3.23.3",
35
35
  "i18n": "^0.14.0",
36
- "jsonwebtoken": "^8.5.1",
36
+ "jsonwebtoken": "^9.0.0",
37
37
  "live-plugin-manager": "^0.16.0",
38
38
  "moment": "^2.27.0",
39
39
  "multer": "^1.4.3",
@@ -44,7 +44,7 @@
44
44
  "passport": "^0.4.1",
45
45
  "passport-custom": "^1.1.1",
46
46
  "passport-http-bearer": "^1.0.1",
47
- "passport-jwt": "4.0.0",
47
+ "passport-jwt": "4.0.1",
48
48
  "passport-totp": "0.0.2",
49
49
  "pg": "^8.2.1",
50
50
  "pluralize": "^8.0.0",
@@ -29,7 +29,8 @@ function initMouseOver() {
29
29
  };
30
30
  node.on("position", update);
31
31
  cy.on("pan zoom resize", update);
32
- buildPreview(node);
32
+ const { type } = node.data();
33
+ if (type === "page" || type === "view") buildPreview(node);
33
34
  });
34
35
 
35
36
  cy.on("mouseout", "node", (event) => {
@@ -41,7 +42,7 @@ function initMouseOver() {
41
42
  }
42
43
 
43
44
  function buildCard(node) {
44
- const { type, label } = node.data();
45
+ const { type, label, isVirtual } = node.data();
45
46
  const html = `
46
47
  <div class="card" style="width: 20rem;">
47
48
  <div class="card-header">
@@ -49,7 +50,7 @@ function buildCard(node) {
49
50
  <h6 class="card-subtitle text-muted">${label}</h6>
50
51
  </div>
51
52
  <div class="card-body">
52
- ${buildTagBadges(node)}
53
+ ${!isVirtual ? buildTagBadges(node) : "<h5>virtual</h5>"}
53
54
  ${buildCardBody(node)}
54
55
  <div>
55
56
  ${type === "page" || type === "view" ? buildPreviewDiv(node) : ""}
@@ -780,16 +780,19 @@ function unique_field_from_rows(
780
780
  const vals = rows
781
781
  .map((o) => o[field_name])
782
782
  .filter((s) => s.startsWith(value));
783
-
783
+ const numtype =
784
+ char_type !== "Lowercase Letters" && char_type !== "Uppercase Letters";
784
785
  if (vals.includes(value) || always_append) {
785
786
  let newname;
786
787
  const stripped = vals
787
788
  .filter((v) => v !== value)
788
789
  .map((s) => s.replace(value_wspace, ""))
789
- .sort();
790
+ .map((s) => (numtype ? +s : s))
791
+ .sort(numtype ? (a, b) => a - b : undefined);
790
792
  if (stripped.length === 0) newname = `${value_wspace}${gen_char(start)}`;
791
793
  else {
792
- const last_i = char_to_i(stripped[stripped.length - 1]);
794
+ const i = char_to_i(stripped[stripped.length - 1]);
795
+ const last_i = numtype ? Math.max(i, start - 1) : i;
793
796
 
794
797
  newname = `${value_wspace}${gen_char(last_i + 1)}`;
795
798
  }
@@ -872,6 +875,7 @@ function split_paste_handler(e) {
872
875
  //const pasted =
873
876
  $elem.val(lines.shift());
874
877
  } else $elem.val(lines.shift());
878
+ $elem.trigger("change");
875
879
  }
876
880
  });
877
881
  }
@@ -370,3 +370,9 @@ table.table-inner-grid td {
370
370
  margin-left: 0;
371
371
  margin-right: 0.5em;
372
372
  }
373
+
374
+ .join-table-header {
375
+ padding: 0.25rem 1rem;
376
+ margin-bottom: 0 !important;
377
+ text-decoration: underline;
378
+ }
@@ -380,8 +380,11 @@ function updateViewPreview() {
380
380
  "CSRF-Token": _sc_globalCsrf,
381
381
  },
382
382
 
383
- error: function (request) {},
383
+ error: function (resp) {
384
+ $("#viewcfg-preview-error").html(resp.responseText || resp.statusText);
385
+ },
384
386
  success: function (res) {
387
+ $("#viewcfg-preview-error").html("");
385
388
  $preview.css({ opacity: 1.0 });
386
389
 
387
390
  //disable functions preview migght try to call
@@ -619,6 +622,11 @@ function build_mobile_app(button) {
619
622
  });
620
623
  }
621
624
 
625
+ function join_field_clicked(e, fieldPath) {
626
+ $("#inputjoin_field").val(fieldPath);
627
+ apply_showif();
628
+ }
629
+
622
630
  (() => {
623
631
  const e = document.querySelector("[data-sidebar-toggler]");
624
632
  let closed = localStorage.getItem("sidebarClosed") === "true";
package/routes/api.js CHANGED
@@ -381,7 +381,10 @@ router.post(
381
381
  res.status(400).json({ error: errors.join(", ") });
382
382
  return;
383
383
  }
384
- const ins_res = await table.tryInsertRow(row, req.user);
384
+ const ins_res = await table.tryInsertRow(
385
+ row,
386
+ req.user || user || { role_id: 10 }
387
+ );
385
388
  if (ins_res.error) res.status(400).json(ins_res);
386
389
  else res.json(ins_res);
387
390
  } else {
@@ -436,7 +439,11 @@ router.post(
436
439
  res.status(400).json({ error: errors.join(", ") });
437
440
  return;
438
441
  }
439
- const ins_res = await table.tryUpdateRow(row, id, req.user);
442
+ const ins_res = await table.tryUpdateRow(
443
+ row,
444
+ id,
445
+ user || req.user || { role_id: 10 }
446
+ );
440
447
 
441
448
  if (ins_res.error) res.status(400).json(ins_res);
442
449
  else res.json(ins_res);
@@ -475,8 +482,15 @@ router.delete(
475
482
  //const fields = await table.getFields();
476
483
  const row = req.body;
477
484
  //readState(row, fields);
478
- await table.deleteRows({ [pk_name]: row[pk_name] });
479
- } else await table.deleteRows({ id });
485
+ await table.deleteRows(
486
+ { [pk_name]: row[pk_name] },
487
+ user || req.user || { role_id: 10 }
488
+ );
489
+ } else
490
+ await table.deleteRows(
491
+ { id },
492
+ user || req.user || { role_id: 10 }
493
+ );
480
494
  res.json({ success: true });
481
495
  } catch (e) {
482
496
  res.status(400).json({ error: e.message });
package/routes/delete.js CHANGED
@@ -36,11 +36,12 @@ router.post(
36
36
  const table = await Table.findOne({ name: tableName });
37
37
  const role = req.user && req.user.id ? req.user.role_id : 10;
38
38
  try {
39
- if (role <= table.min_role_write) await table.deleteRows({ id });
39
+ if (role <= table.min_role_write)
40
+ await table.deleteRows({ id }, req.user || { role_id: 10 });
40
41
  else if (table.ownership_field_id && req.user) {
41
42
  const row = await table.getRow({ id });
42
43
  if (row && table.is_owner(req.user, row))
43
- await table.deleteRows({ id });
44
+ await table.deleteRows({ id }, req.user || { role_id: 10 });
44
45
  else req.flash("error", req.__("Not authorized"));
45
46
  } else
46
47
  req.flash(
package/routes/page.js CHANGED
@@ -61,10 +61,14 @@ router.get(
61
61
  })
62
62
  );
63
63
  } else {
64
- state.log(2, `Page $pagename} not found or not authorized`);
65
- res
66
- .status(404)
67
- .sendWrap(`${pagename} page`, req.__("Page %s not found", pagename));
64
+ if (db_page && !req.user) {
65
+ res.redirect(`/auth/login?dest=${encodeURIComponent(req.originalUrl)}`);
66
+ } else {
67
+ state.log(2, `Page $pagename} not found or not authorized`);
68
+ res
69
+ .status(404)
70
+ .sendWrap(`${pagename} page`, req.__("Page %s not found", pagename));
71
+ }
68
72
  }
69
73
  })
70
74
  );
package/routes/tables.js CHANGED
@@ -887,8 +887,10 @@ router.post(
887
887
  "exttables_min_role_read",
888
888
  exttables_min_role_read
889
889
  );
890
- req.flash("success", req.__("Table saved"));
891
- res.redirect(`/table/${table.name}`);
890
+ if (!req.xhr) {
891
+ req.flash("success", req.__("Table saved"));
892
+ res.redirect(`/table/${table.name}`);
893
+ } else res.json({ success: "ok" });
892
894
  }
893
895
  } else {
894
896
  const { id, _csrf, ...rest } = v;
package/routes/tenant.js CHANGED
@@ -279,18 +279,17 @@ router.post(
279
279
  const tenant_template = getState().getConfig("tenant_template");
280
280
  // tenant creator
281
281
  const user_email = req.user && req.user.email;
282
- // switch to tenant
283
- await switchToTenant(
284
- await insertTenant(
285
- subdomain,
286
- user_email,
287
- description,
288
- tenant_template
289
- ),
290
- newurl
282
+ const tenrow = await insertTenant(
283
+ subdomain,
284
+ user_email,
285
+ description,
286
+ tenant_template
291
287
  );
292
288
  // add tenant to global state
293
289
  add_tenant(subdomain);
290
+
291
+ await switchToTenant(tenrow, newurl);
292
+
294
293
  await create_tenant({
295
294
  t: subdomain,
296
295
  plugin_loader: loadAllPlugins,
package/routes/utils.js CHANGED
@@ -58,7 +58,7 @@ function isAdmin(req, res, next) {
58
58
  ? "/auth/twofa/login/totp"
59
59
  : req.user
60
60
  ? "/"
61
- : "/auth/login"
61
+ : `/auth/login?dest=${encodeURIComponent(req.originalUrl)}`
62
62
  );
63
63
  }
64
64
  }
package/routes/view.js CHANGED
@@ -57,6 +57,10 @@ router.get(
57
57
  role > view.min_role &&
58
58
  !(await view.authorise_get({ query, req, ...view }))
59
59
  ) {
60
+ if (!req.user) {
61
+ res.redirect(`/auth/login?dest=${encodeURIComponent(req.originalUrl)}`);
62
+ return;
63
+ }
60
64
  req.flash("danger", req.__("Not authorized"));
61
65
  state.log(2, `View ${viewname} not authorized`);
62
66
  res.redirect("/");
@@ -8,7 +8,15 @@
8
8
  const Router = require("express-promise-router");
9
9
 
10
10
  const { renderForm, renderBuilder, alert } = require("@saltcorn/markup");
11
- const { p, a, div, script, text, domReady } = require("@saltcorn/markup/tags");
11
+ const {
12
+ p,
13
+ a,
14
+ div,
15
+ script,
16
+ text,
17
+ domReady,
18
+ pre,
19
+ } = require("@saltcorn/markup/tags");
12
20
 
13
21
  const { getState } = require("@saltcorn/data/db/state");
14
22
  const { isAdmin, error_catcher, addOnDoneRedirect } = require("./utils.js");
@@ -59,6 +67,7 @@ router.get(
59
67
  const viewAccessWarning = (view) => {
60
68
  const table = tables.find((t) => t.name === view.table);
61
69
  if (!table) return false;
70
+ if (table.name === "users") return false;
62
71
  if (table.ownership_field_id || table.ownership_formula) return false;
63
72
 
64
73
  return table.min_role_read < view.min_role;
@@ -68,13 +77,12 @@ router.get(
68
77
  hasAccessWarning.length > 0
69
78
  ? alert(
70
79
  "danger",
71
- req.__(
72
- `<p>You have views with a role to access lower than the table role to read,
73
- with no table ownership. In the next version of Saltcorn, this may cause a
74
- denial of access. Users will need to have table read access to any data displayed.</p>
75
- Views potentially affected: %s`,
76
- hasAccessWarning.map((v) => v.name).join(", ")
77
- )
80
+ `<p>${req.__(
81
+ `You have views with a role to access lower than the table role to read, with no table ownership. This may cause a denial of access. Users need to have table read access to any data displayed.`
82
+ )}</p>
83
+ ${req.__("Views potentially affected")}: ${hasAccessWarning
84
+ .map((v) => v.name)
85
+ .join(", ")}`
78
86
  )
79
87
  : "";
80
88
  res.sendWrap(req.__(`Views`), {
@@ -481,8 +489,11 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
481
489
  type: "card",
482
490
  title: req.__("Preview"),
483
491
  contents: div(
484
- { id: "viewcfg-preview", "data-preview-url": previewURL },
485
- script(domReady(`updateViewPreview()`))
492
+ div(pre({ id: "viewcfg-preview-error", class: "text-danger" })),
493
+ div(
494
+ { id: "viewcfg-preview", "data-preview-url": previewURL },
495
+ script(domReady(`updateViewPreview()`))
496
+ )
486
497
  ),
487
498
  },
488
499
  ]
package/systemd.js CHANGED
@@ -3,6 +3,7 @@
3
3
  * @module systemd
4
4
  */
5
5
  const fetch = require("node-fetch");
6
+ const { getState } = require("@saltcorn/data/db/state");
6
7
 
7
8
  /**
8
9
  * @param {number} interval
@@ -21,29 +22,33 @@ const watchDog = (interval, notify, { port }) => {
21
22
  const User = require("@saltcorn/data/models/user");
22
23
  User.count()
23
24
  .then((c) => {
24
- console.log("watchdog user count", c);
25
+ getState().log(5, `watchdog user count ${c}`);
25
26
  notify.watchdog();
26
27
  })
27
28
  .catch((e) => {
28
- console.error(e);
29
+ getState().log(1, e);
29
30
  process.exit(1);
30
31
  });
31
32
  } else {
32
33
  // check we can serve
33
34
  fetch(`http://127.0.0.1:${port}/auth/login`)
34
35
  .then((response) => {
35
- console.log("watchdog request status", response.status);
36
+ getState().log(5, `watchdog request status ${response.status}`);
36
37
  if (response.status < 400) notify.watchdog();
37
38
  else process.exit(1);
38
39
  })
39
40
  .catch((e) => {
40
- console.error(e);
41
+ getState().log(1, e);
41
42
  process.exit(1);
42
43
  });
43
44
  }
44
- } else return notify.watchdog();
45
+ } else {
46
+ getState().log(5, `watchdog with no test`);
47
+ notify.watchdog();
48
+ return;
49
+ }
45
50
  } catch (e) {
46
- console.error(e);
51
+ getState().log(1, e);
47
52
  process.exit(1);
48
53
  }
49
54
  };
@@ -57,6 +62,7 @@ module.exports =
57
62
  (opts) => {
58
63
  try {
59
64
  const notify = require("sd-notify");
65
+ getState().log(4, `systemd notify ready`);
60
66
  notify.ready();
61
67
  const watchdogInterval = notify.watchdogInterval();
62
68
  if (watchdogInterval && watchdogInterval > 0) {
@@ -65,7 +71,12 @@ module.exports =
65
71
  watchDog(interval, notify, opts);
66
72
  }, interval);
67
73
  }
68
- } catch {
74
+ } catch (e) {
69
75
  //ignore, systemd lib not installed
76
+ getState().log(
77
+ 4,
78
+ `Failed to notify systemd on startup (systemd lib not installed?) with error ${e}`
79
+ );
70
80
  }
71
81
  };
82
+ 4;
@@ -18,7 +18,7 @@ const { getState } = require("@saltcorn/data/db/state");
18
18
  const { get_reset_link, generate_email } = require("../auth/resetpw");
19
19
  const i18n = require("i18n");
20
20
  const path = require("path");
21
- const fs = require("fs")
21
+ const fs = require("fs");
22
22
 
23
23
  afterAll(db.close);
24
24
  beforeAll(async () => {
@@ -606,19 +606,19 @@ describe("signup with custom login form", () => {
606
606
 
607
607
  describe("Locale files", () => {
608
608
  it("should be valid JSON", async () => {
609
-
610
- const localeFiles =
611
- await fs.promises.readdir(path.join(__dirname, "..", "/locales"));
612
- expect(localeFiles.length).toBeGreaterThan(3)
613
- expect(localeFiles).toContain("en.json")
609
+ const localeFiles = await fs.promises.readdir(
610
+ path.join(__dirname, "..", "/locales")
611
+ );
612
+ expect(localeFiles.length).toBeGreaterThan(3);
613
+ expect(localeFiles).toContain("en.json");
614
614
  for (const fnm of localeFiles) {
615
615
  const conts = await fs.promises.readFile(
616
616
  path.join(__dirname, "..", "/locales", fnm)
617
- )
618
- expect(conts.length).toBeGreaterThan(1)
617
+ );
618
+ expect(conts.length).toBeGreaterThan(1);
619
619
 
620
- const j = JSON.parse(conts)
621
- expect(Object.keys(j).length).toBeGreaterThan(1)
620
+ const j = JSON.parse(conts);
621
+ expect(Object.keys(j).length).toBeGreaterThan(1);
622
622
  }
623
- })
624
- })
623
+ });
624
+ });
@@ -65,10 +65,10 @@ test("updateQueryStringParameter hash", () => {
65
65
  );
66
66
  });
67
67
  test("unique_field_from_rows test", () => {
68
- $("body").append(`<input id="mkuniq" value="bar"></div>`);
68
+ $("body").append(`<input id="mkuniq6" value="bar"></div>`);
69
69
  unique_field_from_rows(
70
70
  [{ foo: "bar" }, { foo: "bar0" }],
71
- "mkuniq",
71
+ "mkuniq6",
72
72
  "foo",
73
73
  false,
74
74
  0,
@@ -76,12 +76,12 @@ test("unique_field_from_rows test", () => {
76
76
  "Digits",
77
77
  "bar"
78
78
  );
79
- expect($("#mkuniq").val()).toBe("bar1");
79
+ expect($("#mkuniq6").val()).toBe("bar1");
80
80
 
81
- $("body").append(`<input id="mkuniq" value="bar"></div>`);
81
+ $("body").append(`<input id="mkuniq5" value="bar"></div>`);
82
82
  unique_field_from_rows(
83
83
  [{ foo: "bar" }],
84
- "mkuniq",
84
+ "mkuniq5",
85
85
  "foo",
86
86
  false,
87
87
  9,
@@ -89,12 +89,12 @@ test("unique_field_from_rows test", () => {
89
89
  "Digits",
90
90
  "bar"
91
91
  );
92
- expect($("#mkuniq").val()).toBe("bar9");
92
+ expect($("#mkuniq5").val()).toBe("bar9");
93
93
 
94
- $("body").append(`<input id="mkuniq" value="bar"></div>`);
94
+ $("body").append(`<input id="mkuniq4" value="bar"></div>`);
95
95
  unique_field_from_rows(
96
- [{ foo: "bar0" }],
97
- "mkuniq",
96
+ [{ foo: "bar" }, { foo: "bar0" }],
97
+ "mkuniq4",
98
98
  "foo",
99
99
  false,
100
100
  9,
@@ -102,16 +102,25 @@ test("unique_field_from_rows test", () => {
102
102
  "Digits",
103
103
  "bar"
104
104
  );
105
- expect($("#mkuniq").val()).toBe("bar9");
105
+ expect($("#mkuniq4").val()).toBe("bar9");
106
106
 
107
- $("#mkuniq").val("bar");
108
- unique_field_from_rows([], "mkuniq", "foo", false, 0, false, "Digits", "bar");
109
- expect($("#mkuniq").val()).toBe("bar");
107
+ $("#mkuniq6").val("bar");
108
+ unique_field_from_rows(
109
+ [],
110
+ "mkuniq6",
111
+ "foo",
112
+ false,
113
+ 0,
114
+ false,
115
+ "Digits",
116
+ "bar"
117
+ );
118
+ expect($("#mkuniq6").val()).toBe("bar");
110
119
 
111
- $("body").append(`<input id="mkuniq" value="bar"></div>`);
120
+ $("body").append(`<input id="mkuniq3" value="bar"></div>`);
112
121
  unique_field_from_rows(
113
122
  [{ foo: "bar" }, { foo: "bar A" }],
114
- "mkuniq",
123
+ "mkuniq3",
115
124
  "foo",
116
125
  true,
117
126
  0,
@@ -119,13 +128,13 @@ test("unique_field_from_rows test", () => {
119
128
  "Uppercase Letters",
120
129
  "bar"
121
130
  );
122
- expect($("#mkuniq").val()).toBe("bar B");
131
+ expect($("#mkuniq3").val()).toBe("bar B");
123
132
 
124
133
  //skips blanks
125
- $("body").append(`<input id="mkuniq" value="bar"></div>`);
134
+ $("body").append(`<input id="mkuniq2" value="bar"></div>`);
126
135
  unique_field_from_rows(
127
136
  [{ foo: "bar" }, { foo: "bar0" }, { foo: "bar1" }, { foo: "bar3" }],
128
- "mkuniq",
137
+ "mkuniq2",
129
138
  "foo",
130
139
  false,
131
140
  0,
@@ -133,5 +142,42 @@ test("unique_field_from_rows test", () => {
133
142
  "Digits",
134
143
  "bar"
135
144
  );
136
- expect($("#mkuniq").val()).toBe("bar4");
145
+ expect($("#mkuniq2").val()).toBe("bar4");
146
+
147
+ $("body").append(`<input id="mkuniq1" value="bar"></div>`);
148
+ unique_field_from_rows(
149
+ [{ foo: "bar100" }, { foo: "bar101" }, { foo: "bar103" }],
150
+ "mkuniq1",
151
+ "foo",
152
+ false,
153
+ 100,
154
+ true,
155
+ "Digits",
156
+ "bar"
157
+ );
158
+ expect($("#mkuniq1").val()).toBe("bar104");
159
+ $("body").append(`<input id="mkuniq10" value="bar"></div>`);
160
+ unique_field_from_rows(
161
+ [],
162
+ "mkuniq10",
163
+ "foo",
164
+ false,
165
+ 100,
166
+ true,
167
+ "Digits",
168
+ "bar"
169
+ );
170
+ expect($("#mkuniq10").val()).toBe("bar100");
171
+ $("body").append(`<input id="mkuniq11" value="bar"></div>`);
172
+ unique_field_from_rows(
173
+ [{ foo: "bar100" }, { foo: "bar101" }, { foo: "bar35" }, { foo: "bar103" }],
174
+ "mkuniq11",
175
+ "foo",
176
+ false,
177
+ 100,
178
+ true,
179
+ "Digits",
180
+ "bar"
181
+ );
182
+ expect($("#mkuniq11").val()).toBe("bar104");
137
183
  });
@@ -109,6 +109,7 @@ describe("files admin", () => {
109
109
  describe("files edit", () => {
110
110
  it("creates table and view", async () => {
111
111
  const table = await Table.create("thefiletable");
112
+ await table.update({ min_role_read: 8, min_role_write: 8 });
112
113
  await Field.create({
113
114
  table,
114
115
  name: "first_name",
@@ -24,8 +24,8 @@ const pack = {
24
24
  tables: [
25
25
  {
26
26
  name: "cats",
27
- min_role_read: 1,
28
- min_role_write: 1,
27
+ min_role_read: 10,
28
+ min_role_write: 10,
29
29
  versioned: false,
30
30
  fields: [
31
31
  {
@@ -33,9 +33,9 @@ describe("Table Endpoints", () => {
33
33
  .post("/table/")
34
34
  .send("name=mypostedtable")
35
35
  .set("Cookie", loginCookie)
36
- .expect(toRedirect("/table/7"));
36
+ .expect(toRedirect("/table/10"));
37
37
  await request(app)
38
- .get("/table/7")
38
+ .get("/table/10")
39
39
  .set("Cookie", loginCookie)
40
40
  .expect(toInclude("mypostedtable"));
41
41
  await request(app)
@@ -114,7 +114,7 @@ describe("Table Endpoints", () => {
114
114
  });
115
115
  it("should edit external table role", async () => {
116
116
  const loginCookie = await getAdminLoginCookie();
117
- getState().registerPlugin("mock_plugin", plugin_with_routes);
117
+ getState().registerPlugin("mock_plugin", plugin_with_routes());
118
118
  const app = await getApp({ disableCsrf: true });
119
119
  await request(app)
120
120
  .post(`/table`)
@@ -149,7 +149,7 @@ Pencil, 0.5,2, t`;
149
149
  .set("Cookie", loginCookie)
150
150
  .field("name", "expenses")
151
151
  .attach("file", Buffer.from(csv, "utf-8"))
152
- .expect(toRedirect("/table/8"));
152
+ .expect(toRedirect("/table/11"));
153
153
  });
154
154
  it("should upload csv to existing table", async () => {
155
155
  const csv = `author,Pages
@@ -16,7 +16,6 @@ const { getState } = require("@saltcorn/data/db/state");
16
16
  afterAll(db.close);
17
17
  jest.setTimeout(10000);
18
18
 
19
-
20
19
  beforeAll(async () => {
21
20
  if (!db.isSQLite) {
22
21
  await db.query(`drop schema if exists test2 cascade`);
@@ -94,12 +93,9 @@ describe("tenant routes", () => {
94
93
  .set("Cookie", loginCookie)
95
94
  .expect(toRedirect("/tenant/list"));
96
95
  });
97
-
98
96
  } else {
99
-
100
97
  it("does not support tenants on SQLite", async () => {
101
98
  expect(db.isSQLite).toBe(true);
102
99
  });
103
-
104
100
  }
105
101
  });
@@ -2,6 +2,7 @@ const request = require("supertest");
2
2
  const getApp = require("../app");
3
3
  const {
4
4
  toRedirect,
5
+ getAdminLoginCookie,
5
6
  getStaffLoginCookie,
6
7
  itShouldRedirectUnauthToLogin,
7
8
  toInclude,
@@ -33,7 +34,7 @@ describe("nonexisting view", () => {
33
34
  itShouldRedirectUnauthToLogin("/view/patlist", "/");
34
35
  });
35
36
  describe("view patients list endpoint", () => {
36
- itShouldRedirectUnauthToLogin("/view/patientlist", "/");
37
+ itShouldRedirectUnauthToLogin("/view/patientlist");
37
38
 
38
39
  it("should show view to staff", async () => {
39
40
  const loginCookie = await getStaffLoginCookie();
@@ -78,17 +79,18 @@ describe("edit view", () => {
78
79
  });
79
80
  it("should submit edit", async () => {
80
81
  const app = await getApp({ disableCsrf: true });
82
+ const loginCookie = await getAdminLoginCookie();
81
83
  await request(app)
82
84
  .post("/view/authoredit")
85
+ .set("Cookie", loginCookie)
83
86
  .send("author=Chekov")
84
-
85
87
  .expect(toRedirect("/view/authorlist"));
86
88
  });
87
89
  });
88
90
 
89
91
  describe("view with routes", () => {
90
92
  it("should enable", async () => {
91
- getState().registerPlugin("mock_plugin", plugin_with_routes);
93
+ getState().registerPlugin("mock_plugin", plugin_with_routes());
92
94
  expect(getState().viewtemplates.ViewWithRoutes.name).toBe("ViewWithRoutes");
93
95
  const table = await Table.findOne({ name: "books" });
94
96