@saltcorn/server 0.7.4-beta.3 → 0.8.0-beta.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.
@@ -16,6 +16,7 @@ const {
16
16
  post_dropdown_item,
17
17
  renderBuilder,
18
18
  settingsDropdown,
19
+ alert,
19
20
  } = require("@saltcorn/markup");
20
21
  const {
21
22
  //span,
@@ -26,11 +27,13 @@ const {
26
27
  a,
27
28
  div,
28
29
  //button,
30
+ script,
29
31
  text,
32
+ domReady,
30
33
  } = require("@saltcorn/markup/tags");
31
34
 
32
35
  const { getState } = require("@saltcorn/data/db/state");
33
- const { isAdmin, error_catcher } = require("./utils.js");
36
+ const { isAdmin, error_catcher, addOnDoneRedirect } = require("./utils.js");
34
37
  const { setTableRefs, viewsList } = require("./common_lists");
35
38
  const Form = require("@saltcorn/data/models/form");
36
39
  const Field = require("@saltcorn/data/models/field");
@@ -39,7 +42,6 @@ const View = require("@saltcorn/data/models/view");
39
42
  const Workflow = require("@saltcorn/data/models/workflow");
40
43
  const User = require("@saltcorn/data/models/user");
41
44
  const Page = require("@saltcorn/data/models/page");
42
- const Tag = require("@saltcorn/data/models/tag");
43
45
  const db = require("@saltcorn/data/db");
44
46
 
45
47
  const { add_to_menu } = require("@saltcorn/admin-models/models/pack");
@@ -76,6 +78,26 @@ router.get(
76
78
 
77
79
  const viewMarkup = await viewsList(views, req);
78
80
  const tables = await Table.find();
81
+ const viewAccessWarning = (view) => {
82
+ const table = tables.find((t) => t.name === view.table);
83
+ if (!table) return false;
84
+ if (table.ownership_field_id || table.ownership_formula) return false;
85
+
86
+ return table.min_role_read < view.min_role;
87
+ };
88
+ const hasAccessWarning = views.filter(viewAccessWarning);
89
+ const accessWarning =
90
+ hasAccessWarning.length > 0
91
+ ? alert(
92
+ "danger",
93
+ `<p>You have views with a role to access lower than the table role to read,
94
+ with no table ownership. In the next version of Saltcorn, this may cause a
95
+ denial of access. Users will need to have table read access to any data displayed.</p>
96
+ Views potentially affected: ${hasAccessWarning
97
+ .map((v) => v.name)
98
+ .join(", ")}`
99
+ )
100
+ : "";
79
101
  res.sendWrap(req.__(`Views`), {
80
102
  above: [
81
103
  {
@@ -87,17 +109,18 @@ router.get(
87
109
  class: "mt-0",
88
110
  title: req.__("Your views"),
89
111
  contents: [
112
+ accessWarning,
90
113
  viewMarkup,
91
114
  tables.length > 0
92
115
  ? a(
93
- { href: `/viewedit/new`, class: "btn btn-primary" },
94
- req.__("Create view")
95
- )
116
+ { href: `/viewedit/new`, class: "btn btn-primary" },
117
+ req.__("Create view")
118
+ )
96
119
  : p(
97
- req.__(
98
- "You must create at least one table before you can create views."
99
- )
100
- ),
120
+ req.__(
121
+ "You must create at least one table before you can create views."
122
+ )
123
+ ),
101
124
  ],
102
125
  },
103
126
  ],
@@ -129,7 +152,7 @@ const viewForm = async (req, tableOptions, roles, pages, values) => {
129
152
  .map(([k, v]) => k);
130
153
  const slugOptions = await Table.allSlugOptions();
131
154
  return new Form({
132
- action: "/viewedit/save",
155
+ action: addOnDoneRedirect("/viewedit/save", req),
133
156
  submitLabel: req.__("Configure") + " &raquo;",
134
157
  blurb: req.__("First, please give some basic information about the view."),
135
158
  fields: [
@@ -337,7 +360,6 @@ router.post(
337
360
  const pages = await Page.find();
338
361
  const form = await viewForm(req, tableOptions, roles, pages);
339
362
  const result = form.validate(req.body);
340
-
341
363
  const sendForm = (form) => {
342
364
  res.sendWrap(req.__(`Edit view`), {
343
365
  above: [
@@ -397,7 +419,12 @@ router.post(
397
419
  else v.configuration = {};
398
420
  await View.create(v);
399
421
  }
400
- res.redirect(`/viewedit/config/${encodeURIComponent(v.name)}`);
422
+ res.redirect(
423
+ addOnDoneRedirect(
424
+ `/viewedit/config/${encodeURIComponent(v.name)}`,
425
+ req
426
+ )
427
+ );
401
428
  }
402
429
  } else {
403
430
  sendForm(form);
@@ -414,7 +441,7 @@ router.post(
414
441
  * @returns {void}
415
442
  */
416
443
  const respondWorkflow = (view, wf, wfres, req, res) => {
417
- const wrap = (contents, noCard) => ({
444
+ const wrap = (contents, noCard, previewURL) => ({
418
445
  above: [
419
446
  {
420
447
  type: "breadcrumbs",
@@ -430,6 +457,12 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
430
457
  title: wfres.title,
431
458
  contents,
432
459
  },
460
+ ...previewURL ? [{
461
+ type: "card",
462
+ title: req.__("Preview"),
463
+ contents: div({ id: "viewcfg-preview", "data-preview-url": previewURL },
464
+ script(domReady(`updateViewPreview()`))),
465
+ }] : []
433
466
  ],
434
467
  });
435
468
  if (wfres.flash) req.flash(wfres.flash[0], wfres.flash[1]);
@@ -452,7 +485,7 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
452
485
  },
453
486
  ],
454
487
  },
455
- wrap(renderForm(wfres.renderForm, req.csrfToken()))
488
+ wrap(renderForm(wfres.renderForm, req.csrfToken()), false, wfres.previewURL)
456
489
  );
457
490
  else if (wfres.renderBuilder) {
458
491
  wfres.renderBuilder.options.view_id = view.id;
@@ -666,14 +699,14 @@ router.post(
666
699
  const view = await View.findOne({ id });
667
700
  const roles = await User.get_roles();
668
701
  const roleRow = roles.find((r) => r.id === +role);
669
- if (roleRow && view)
670
- req.flash(
671
- "success",
672
- req.__(`Minimum role for %s updated to %s`, view.name, roleRow.role)
673
- );
674
- else req.flash("success", req.__(`Minimum role updated`));
675
-
676
- res.redirect("/viewedit");
702
+ const message =
703
+ roleRow && view
704
+ ? req.__(`Minimum role for %s updated to %s`, view.name, roleRow.role)
705
+ : req.__(`Minimum role updated`);
706
+ if (!req.xhr) {
707
+ req.flash("success", message);
708
+ res.redirect("/viewedit");
709
+ } else res.json({ okay: true, responseText: message });
677
710
  })
678
711
  );
679
712
 
package/serve.js CHANGED
@@ -99,11 +99,16 @@ const workerDispatchMsg = ({ tenant, ...msg }) => {
99
99
  db.runWithTenant(tenant, () => workerDispatchMsg(msg));
100
100
  return;
101
101
  }
102
+
102
103
  if (msg.refresh_plugin_cfg) {
103
104
  Plugin.findOne({ name: msg.refresh_plugin_cfg }).then((plugin) => {
104
105
  if (plugin) loadPlugin(plugin);
105
106
  });
106
107
  }
108
+ if (!getState()) {
109
+ console.error("no State for tenant", tenant)
110
+ return
111
+ }
107
112
  if (msg.refresh) getState()[`refresh_${msg.refresh}`](true);
108
113
  if (msg.createTenant) {
109
114
  const tenant_template = getState().getConfig("tenant_template");
@@ -68,9 +68,11 @@ describe("admin page", () => {
68
68
  .expect(toInclude("Site identity settings"));
69
69
  });
70
70
  adminPageContains([
71
+ ["/admin", "Site identity"],
71
72
  ["/admin/backup", "Download a backup"],
72
73
  ["/admin/email", "Email settings"],
73
74
  ["/admin/system", "Restart server"],
75
+ ["/admin/dev", "Development"],
74
76
  ]);
75
77
  adminPageContains([
76
78
  ["/useradmin", "Create user"],
@@ -18,6 +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
22
 
22
23
  afterAll(db.close);
23
24
  beforeAll(async () => {
@@ -602,3 +603,22 @@ describe("signup with custom login form", () => {
602
603
  expect(userrow.height).toBe(15);
603
604
  });
604
605
  });
606
+
607
+ describe("Locale files", () => {
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")
614
+ for (const fnm of localeFiles) {
615
+ const conts = await fs.promises.readFile(
616
+ path.join(__dirname, "..", "/locales", fnm)
617
+ )
618
+ expect(conts.length).toBeGreaterThan(1)
619
+
620
+ const j = JSON.parse(conts)
621
+ expect(Object.keys(j).length).toBeGreaterThan(1)
622
+ }
623
+ })
624
+ })
@@ -39,22 +39,13 @@ describe("files admin", () => {
39
39
  await request(app)
40
40
  .get("/files")
41
41
  .set("Cookie", loginCookie)
42
- .expect(toInclude("Size (KiB)"));
42
+ .expect(toInclude("Upload file"));
43
43
  });
44
44
  it("download file", async () => {
45
45
  const app = await getApp({ disableCsrf: true });
46
46
  const loginCookie = await getStaffLoginCookie();
47
47
  await request(app)
48
- .get("/files/download/2")
49
- .set("Cookie", loginCookie)
50
- .expect(toSucceed());
51
- });
52
-
53
- it("serve file", async () => {
54
- const app = await getApp({ disableCsrf: true });
55
- const loginCookie = await getStaffLoginCookie();
56
- await request(app)
57
- .get("/files/serve/2")
48
+ .get("/files/download/rick.png")
58
49
  .set("Cookie", loginCookie)
59
50
  .expect(toSucceed());
60
51
  });
@@ -71,38 +62,38 @@ describe("files admin", () => {
71
62
  const app = await getApp({ disableCsrf: true });
72
63
  const loginCookie = await getStaffLoginCookie();
73
64
  await request(app)
74
- .get("/files/serve/22")
65
+ .get("/files/serve/missingfile.foo")
75
66
  .set("Cookie", loginCookie)
76
67
  .expect(404);
77
68
  });
78
69
  it("not serve file to public", async () => {
79
70
  const app = await getApp({ disableCsrf: true });
80
- await request(app).get("/files/serve/2").expect(toRedirect("/"));
71
+ await request(app).get("/files/serve/rick.png").expect(404);
81
72
  });
82
73
  it("not download file to public", async () => {
83
74
  const app = await getApp({ disableCsrf: true });
84
- await request(app).get("/files/download/2").expect(toRedirect("/"));
75
+ await request(app).get("/files/download/rick.png").expect(404);
85
76
  });
86
77
  it("set file min role", async () => {
87
78
  const app = await getApp({ disableCsrf: true });
88
79
  const loginCookie = await getAdminLoginCookie();
89
80
  await request(app)
90
- .post("/files/setrole/2")
81
+ .post("/files/setrole/rick.png")
91
82
  .set("Cookie", loginCookie)
92
83
  .send("role=10")
93
- .expect(toRedirect("/files"));
84
+ .expect(toRedirect("/files?dir=."));
94
85
  });
95
86
  it("serve file to public after role change", async () => {
96
87
  const app = await getApp({ disableCsrf: true });
97
- await request(app).get("/files/serve/2").expect(toSucceed());
88
+ await request(app).get("/files/serve/rick.png").expect(toSucceed());
98
89
  });
99
90
  it("delete file", async () => {
100
91
  const app = await getApp({ disableCsrf: true });
101
92
  const loginCookie = await getAdminLoginCookie();
102
93
  await request(app)
103
- .post("/files/delete/2")
94
+ .post("/files/delete/rick.png")
104
95
  .set("Cookie", loginCookie)
105
- .expect(toRedirect("/files"));
96
+ .expect(toRedirect("/files?dir=."));
106
97
  });
107
98
  it("upload file", async () => {
108
99
  const app = await getApp({ disableCsrf: true });
@@ -112,7 +103,7 @@ describe("files admin", () => {
112
103
  .set("Cookie", loginCookie)
113
104
  .attach("file", Buffer.from("helloiamasmallfile", "utf-8"))
114
105
 
115
- .expect(toRedirect("/files"));
106
+ .expect(toRedirect("/files?dir=."));
116
107
  });
117
108
  });
118
109
  describe("files edit", () => {
@@ -5,14 +5,16 @@ const getApp = require("../app");
5
5
  const {
6
6
  toRedirect,
7
7
  getAdminLoginCookie,
8
- getStaffLoginCookie,
8
+ //getStaffLoginCookie,
9
9
  itShouldRedirectUnauthToLogin,
10
10
  toInclude,
11
- toNotInclude,
11
+ //toNotInclude,
12
12
  } = require("../auth/testhelp");
13
13
  const { getState } = require("@saltcorn/data/db/state");
14
14
 
15
15
  afterAll(db.close);
16
+ jest.setTimeout(10000);
17
+
16
18
 
17
19
  beforeAll(async () => {
18
20
  if (!db.isSQLite) {