@saltcorn/server 0.8.5-beta.3 → 0.8.5-beta.5

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/admin.js CHANGED
@@ -10,6 +10,7 @@ const {
10
10
  error_catcher,
11
11
  getGitRevision,
12
12
  setTenant,
13
+ admin_config_route,
13
14
  get_sys_info,
14
15
  } = require("./utils.js");
15
16
  const Table = require("@saltcorn/data/models/table");
@@ -84,7 +85,6 @@ const {
84
85
  //send_files_page,
85
86
  config_fields_form,
86
87
  save_config_from_form,
87
- flash_restart_if_required,
88
88
  } = require("../markup/admin.js");
89
89
  const packagejson = require("../package.json");
90
90
  const Form = require("@saltcorn/data/models/form");
@@ -107,52 +107,6 @@ const Crash = require("@saltcorn/data/models/crash");
107
107
  const router = new Router();
108
108
  module.exports = router;
109
109
 
110
- /**
111
- * Site identity form
112
- * @param {object} req -http request
113
- * @returns {Promise<Form>} form
114
- */
115
- const site_id_form = (req) =>
116
- config_fields_form({
117
- req,
118
- field_names: [
119
- "site_name",
120
- "timezone",
121
- "base_url",
122
- ...(getConfigFile() ? ["multitenancy_enabled"] : []),
123
- { section_header: "Logo image" },
124
- "site_logo_id",
125
- "favicon_id",
126
- { section_header: "Custom code" },
127
- "page_custom_css",
128
- "page_custom_html",
129
- { section_header: "Extension store" },
130
- "plugins_store_endpoint",
131
- "packs_store_endpoint",
132
- ],
133
- action: "/admin",
134
- submitLabel: req.__("Save"),
135
- });
136
- /**
137
- * Email settings form
138
- * @param {object} req request
139
- * @returns {Promise<Form>} form
140
- */
141
- const email_form = async (req) => {
142
- return await config_fields_form({
143
- req,
144
- field_names: [
145
- "smtp_host",
146
- "smtp_username",
147
- "smtp_password",
148
- "smtp_port",
149
- "smtp_secure",
150
- "email_from",
151
- ],
152
- action: "/admin/email",
153
- });
154
- };
155
-
156
110
  const app_files_table = (files, buildDirName, req) =>
157
111
  mkTable(
158
112
  [
@@ -182,17 +136,27 @@ const app_files_table = (files, buildDirName, req) =>
182
136
  files
183
137
  );
184
138
 
185
- /**
186
- * Router get /
187
- * @name get
188
- * @function
189
- * @memberof module:routes/admin~routes/adminRouter
190
- */
191
- router.get(
192
- "/",
193
- isAdmin,
194
- error_catcher(async (req, res) => {
195
- const form = await site_id_form(req);
139
+ admin_config_route({
140
+ router,
141
+ path: "/",
142
+ super_path: "/admin",
143
+ flash: "Site identity settings updated",
144
+ field_names: [
145
+ "site_name",
146
+ "timezone",
147
+ "base_url",
148
+ ...(getConfigFile() ? ["multitenancy_enabled"] : []),
149
+ { section_header: "Logo image" },
150
+ "site_logo_id",
151
+ "favicon_id",
152
+ { section_header: "Custom code" },
153
+ "page_custom_css",
154
+ "page_custom_html",
155
+ { section_header: "Extension store" },
156
+ "plugins_store_endpoint",
157
+ "packs_store_endpoint",
158
+ ],
159
+ response(form, req, res) {
196
160
  send_admin_page({
197
161
  res,
198
162
  req,
@@ -204,53 +168,23 @@ router.get(
204
168
  contents: [renderForm(form, req.csrfToken())],
205
169
  },
206
170
  });
207
- })
208
- );
209
-
210
- /**
211
- * @name post
212
- * @function
213
- * @memberof module:routes/admin~routes/adminRouter
214
- */
215
- router.post(
216
- "/",
217
- isAdmin,
218
- error_catcher(async (req, res) => {
219
- const form = await site_id_form(req);
220
- form.validate(req.body);
221
- if (form.hasErrors) {
222
- send_admin_page({
223
- res,
224
- req,
225
- active_sub: "Site identity",
226
- contents: {
227
- type: "card",
228
- title: req.__("Site identity settings"),
229
- contents: [renderForm(form, req.csrfToken())],
230
- },
231
- });
232
- } else {
233
- flash_restart_if_required(form, req);
234
- await save_config_from_form(form);
235
-
236
- if (!req.xhr) {
237
- req.flash("success", req.__("Site identity settings updated"));
238
- res.redirect("/admin");
239
- } else res.json({ success: "ok" });
240
- }
241
- })
242
- );
171
+ },
172
+ });
243
173
 
244
- /**
245
- * @name get/email
246
- * @function
247
- * @memberof module:routes/admin~routes/adminRouter
248
- */
249
- router.get(
250
- "/email",
251
- isAdmin,
252
- error_catcher(async (req, res) => {
253
- const form = await email_form(req);
174
+ admin_config_route({
175
+ router,
176
+ path: "/email",
177
+ super_path: "/admin",
178
+ flash: "Email settings updated",
179
+ field_names: [
180
+ "smtp_host",
181
+ "smtp_username",
182
+ "smtp_password",
183
+ "smtp_port",
184
+ "smtp_secure",
185
+ "email_from",
186
+ ],
187
+ response(form, req, res) {
254
188
  send_admin_page({
255
189
  res,
256
190
  req,
@@ -272,8 +206,8 @@ router.get(
272
206
  ],
273
207
  },
274
208
  });
275
- })
276
- );
209
+ },
210
+ });
277
211
 
278
212
  /**
279
213
  * @name get/send-test-email
@@ -305,38 +239,6 @@ router.get(
305
239
  })
306
240
  );
307
241
 
308
- /**
309
- * @name post/email
310
- * @function
311
- * @memberof module:routes/admin~routes/adminRouter
312
- */
313
- router.post(
314
- "/email",
315
- isAdmin,
316
- error_catcher(async (req, res) => {
317
- const form = await email_form(req);
318
- form.validate(req.body);
319
- if (form.hasErrors) {
320
- send_admin_page({
321
- res,
322
- req,
323
- active_sub: "Email",
324
- contents: {
325
- type: "card",
326
- title: req.__("Email settings"),
327
- contents: [renderForm(form, req.csrfToken())],
328
- },
329
- });
330
- } else {
331
- await save_config_from_form(form);
332
- if (!req.xhr) {
333
- req.flash("success", req.__("Email settings updated"));
334
- res.redirect("/admin/email");
335
- } else res.json({ success: "ok" });
336
- }
337
- })
338
- );
339
-
340
242
  /**
341
243
  * @name get/backup
342
244
  * @function
@@ -1321,12 +1223,16 @@ router.get(
1321
1223
  "/configuration-check",
1322
1224
  isAdmin,
1323
1225
  error_catcher(async (req, res) => {
1324
- const filename = `${moment().format("YYYYMMDDHHmm")}.html`;
1226
+ const start = new Date();
1227
+ const filename = `${moment(start).format("YYYYMMDDHHmm")}.html`;
1325
1228
  await File.new_folder("configuration_checks");
1326
1229
  const go = async () => {
1327
1230
  const { passes, errors, pass, warnings } = await runConfigurationCheck(
1328
1231
  req
1329
1232
  );
1233
+ const end = new Date();
1234
+ const secs = Math.round((end.getTime() - start.getTime()) / 1000);
1235
+
1330
1236
  const mkError = (err) =>
1331
1237
  div(
1332
1238
  { class: "alert alert-danger", role: "alert" },
@@ -1355,6 +1261,11 @@ router.get(
1355
1261
  h3("Passes"),
1356
1262
 
1357
1263
  pre(code(passes.join("\n")))
1264
+ ) +
1265
+ p(
1266
+ `Configuration check completed in ${
1267
+ secs > 60 ? `${Math.floor(secs / 60)}m ${secs % 60}s` : secs + "s"
1268
+ }`
1358
1269
  );
1359
1270
  await File.from_contents(
1360
1271
  filename,
@@ -1958,42 +1869,31 @@ router.post(
1958
1869
  })
1959
1870
  );
1960
1871
 
1961
- /**
1962
- * Developer settings form
1963
- * @param {object} req request
1964
- * @returns {Promise<Form>} form
1965
- */
1966
- const dev_form = async (req) => {
1967
- const role_to_create_tenant = +getRootState().getConfig(
1968
- "role_to_create_tenant"
1969
- );
1970
- const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
1872
+ admin_config_route({
1873
+ router,
1874
+ path: "/dev",
1875
+ super_path: "/admin",
1876
+ flash: "Development mode settings updated",
1877
+ async get_form(req) {
1878
+ const tenants_set_npm_modules = getRootState().getConfig(
1879
+ "tenants_set_npm_modules",
1880
+ false
1881
+ );
1882
+ const isRoot = db.getTenantSchema() === db.connectObj.default_schema;
1971
1883
 
1972
- return await config_fields_form({
1973
- req,
1974
- field_names: [
1975
- "development_mode",
1976
- "log_sql",
1977
- "log_client_errors",
1978
- "log_level",
1979
- ...(isRoot || role_to_create_tenant < 10
1980
- ? ["npm_available_js_code"]
1981
- : []),
1982
- ],
1983
- action: "/admin/dev",
1984
- });
1985
- };
1986
- /**
1987
- * Developer Mode page
1988
- * @name get/dev
1989
- * @function
1990
- * @memberof module:routes/admin~routes/adminRouter
1991
- */
1992
- router.get(
1993
- "/dev",
1994
- isAdmin,
1995
- error_catcher(async (req, res) => {
1996
- const form = await dev_form(req);
1884
+ return await config_fields_form({
1885
+ req,
1886
+ field_names: [
1887
+ "development_mode",
1888
+ "log_sql",
1889
+ "log_client_errors",
1890
+ "log_level",
1891
+ ...(isRoot || tenants_set_npm_modules ? ["npm_available_js_code"] : []),
1892
+ ],
1893
+ action: "/admin/dev",
1894
+ });
1895
+ },
1896
+ response(form, req, res) {
1997
1897
  send_admin_page({
1998
1898
  res,
1999
1899
  req,
@@ -2002,51 +1902,42 @@ router.get(
2002
1902
  type: "card",
2003
1903
  title: req.__("Development settings"),
2004
1904
  titleAjaxIndicator: true,
2005
- contents: [
2006
- renderForm(form, req.csrfToken()) /*,
2007
- a(
2008
- {
2009
- id: "testemail",
2010
- href: "/admin/send-test-email",
2011
- class: "btn btn-primary",
2012
- },
2013
- req.__("Send test email")
2014
- ),*/,
2015
- ],
1905
+ contents: [renderForm(form, req.csrfToken())],
2016
1906
  },
2017
1907
  });
2018
- })
2019
- );
1908
+ },
1909
+ });
2020
1910
 
2021
- /**
2022
- * Development mode
2023
- * @name post/email
2024
- * @function
2025
- * @memberof module:routes/admin~routes/adminRouter
2026
- */
2027
- router.post(
2028
- "/dev",
2029
- isAdmin,
2030
- error_catcher(async (req, res) => {
2031
- const form = await dev_form(req);
2032
- form.validate(req.body);
2033
- if (form.hasErrors) {
2034
- send_admin_page({
2035
- res,
2036
- req,
2037
- active_sub: "Development",
2038
- contents: {
2039
- type: "card",
2040
- title: req.__("Development settings"),
2041
- contents: [renderForm(form, req.csrfToken())],
2042
- },
2043
- });
2044
- } else {
2045
- await save_config_from_form(form);
2046
- if (!req.xhr) {
2047
- req.flash("success", req.__("Development mode settings updated"));
2048
- res.redirect("/admin/dev");
2049
- } else res.json({ success: "ok" });
2050
- }
2051
- })
2052
- );
1911
+ admin_config_route({
1912
+ router,
1913
+ path: "/notifications",
1914
+ super_path: "/admin",
1915
+ field_names: [
1916
+ "notification_in_menu",
1917
+ { section_header: "Progressive Web Application" },
1918
+ "pwa_enabled",
1919
+ { name: "pwa_display", showIf: { pwa_enabled: true } },
1920
+ { name: "pwa_set_colors", showIf: { pwa_enabled: true } },
1921
+ {
1922
+ name: "pwa_theme_color",
1923
+ showIf: { pwa_enabled: true, pwa_set_colors: true },
1924
+ },
1925
+ {
1926
+ name: "pwa_background_color",
1927
+ showIf: { pwa_enabled: true, pwa_set_colors: true },
1928
+ },
1929
+ ],
1930
+ response(form, req, res) {
1931
+ send_admin_page({
1932
+ res,
1933
+ req,
1934
+ active_sub: "Notifications",
1935
+ contents: {
1936
+ type: "card",
1937
+ title: req.__("Notification settings"),
1938
+ titleAjaxIndicator: true,
1939
+ contents: [renderForm(form, req.csrfToken())],
1940
+ },
1941
+ });
1942
+ },
1943
+ });
package/routes/api.js CHANGED
@@ -31,6 +31,7 @@ const {
31
31
  readState,
32
32
  strictParseInt,
33
33
  } = require("@saltcorn/data/plugin-helper");
34
+ const Crash = require("@saltcorn/data/models/crash");
34
35
 
35
36
  /**
36
37
  * @type {object}
@@ -66,7 +67,7 @@ const limitFields = (fields) => (r) => {
66
67
  * @param {Table} table
67
68
  * @returns {boolean}
68
69
  */
69
- function accessAllowedRead(req, user, table) {
70
+ function accessAllowedRead(req, user, table, allow_ownership) {
70
71
  const role =
71
72
  req.user && req.user.id
72
73
  ? req.user.role_id
@@ -74,7 +75,12 @@ function accessAllowedRead(req, user, table) {
74
75
  ? user.role_id
75
76
  : 10;
76
77
 
77
- return role <= table.min_role_read;
78
+ return (
79
+ role <= table.min_role_read ||
80
+ ((req.user?.id || user?.id) &&
81
+ allow_ownership &&
82
+ (table.ownership_field_id || table.ownership_formula))
83
+ );
78
84
  }
79
85
 
80
86
  /**
@@ -92,7 +98,11 @@ function accessAllowedWrite(req, user, table) {
92
98
  ? user.role_id
93
99
  : 10;
94
100
 
95
- return role <= table.min_role_write;
101
+ return (
102
+ role <= table.min_role_write ||
103
+ ((req.user?.id || user?.id) &&
104
+ (table.ownership_field_id || table.ownership_formula))
105
+ );
96
106
  }
97
107
  /**
98
108
  * Check that user has right to trigger call
@@ -126,6 +136,7 @@ router.post(
126
136
  const view = await View.findOne({ name: viewName });
127
137
  const db = require("@saltcorn/data/db");
128
138
  if (!view) {
139
+ getState().log(3, `API viewQuery ${view.name} not found`);
129
140
  res.status(404).json({
130
141
  error: req.__("View %s not found", viewName),
131
142
  view: viewName,
@@ -152,6 +163,10 @@ router.post(
152
163
  const resp = await queries[queryName](...args, true);
153
164
  res.json({ success: resp, alerts: getFlashes(req) });
154
165
  } else {
166
+ getState().log(
167
+ 3,
168
+ `API viewQuery ${view.name} ${queryName} not found`
169
+ );
155
170
  res.status(404).json({
156
171
  error: req.__("Query %s not found", queryName),
157
172
  view: viewName,
@@ -163,6 +178,7 @@ router.post(
163
178
  });
164
179
  }
165
180
  } else {
181
+ getState().log(3, `API viewQuery ${view.name} not authorized`);
166
182
  res.status(401).json({ error: req.__("Not authorized") });
167
183
  }
168
184
  }
@@ -208,6 +224,10 @@ router.get(
208
224
  }
209
225
  res.json({ success: dvs });
210
226
  } else {
227
+ getState().log(
228
+ 3,
229
+ `API distinct ${table.name}.${fieldName} not authorized`
230
+ );
211
231
  res.status(401).json({ error: req.__("Not authorized") });
212
232
  }
213
233
  }
@@ -234,6 +254,7 @@ router.get(
234
254
  : { name: tableName }
235
255
  );
236
256
  if (!table) {
257
+ getState().log(3, `API get ${tableName} table not found`);
237
258
  res.status(404).json({ error: req.__("Not found") });
238
259
  return;
239
260
  }
@@ -242,11 +263,13 @@ router.get(
242
263
  ["api-bearer", "jwt"],
243
264
  { session: false },
244
265
  async function (err, user, info) {
245
- if (accessAllowedRead(req, user, table)) {
266
+ if (accessAllowedRead(req, user, table, true)) {
246
267
  let rows;
247
268
  if (versioncount === "on") {
248
269
  const joinOpts = {
249
270
  orderBy: "id",
271
+ forUser: req.user || user || { role_id: 10 },
272
+ forPublic: !(req.user || user),
250
273
  aggregations: {
251
274
  _versions: {
252
275
  table: table.name + "__history",
@@ -266,12 +289,22 @@ router.get(
266
289
  state: req_query,
267
290
  table,
268
291
  });
269
- rows = await table.getRows(qstate);
292
+ rows = await table.getRows(qstate, {
293
+ forPublic: !(req.user || user),
294
+ forUser: req.user || user,
295
+ });
270
296
  } else {
271
- rows = await table.getRows();
297
+ rows = await table.getRows(
298
+ {},
299
+ {
300
+ forPublic: !(req.user || user),
301
+ forUser: req.user || user,
302
+ }
303
+ );
272
304
  }
273
305
  res.json({ success: rows.map(limitFields(fields)) });
274
306
  } else {
307
+ getState().log(3, `API get ${table.name} not authorized`);
275
308
  res.status(401).json({ error: req.__("Not authorized") });
276
309
  }
277
310
  }
@@ -301,6 +334,7 @@ router.post(
301
334
  });
302
335
 
303
336
  if (!trigger) {
337
+ getState().log(3, `API action ${actionname} not found`);
304
338
  res.status(400).json({ error: req.__("Not found") });
305
339
  return;
306
340
  }
@@ -314,13 +348,16 @@ router.post(
314
348
  const resp = await action.run({
315
349
  configuration: trigger.configuration,
316
350
  body: req.body,
351
+ row: req.body,
317
352
  req,
318
353
  });
319
354
  res.json({ success: true, data: resp });
320
355
  } catch (e) {
356
+ Crash.create(e, req);
321
357
  res.status(400).json({ success: false, error: e.message });
322
358
  }
323
359
  } else {
360
+ getState().log(3, `API action ${actionname} not authorized`);
324
361
  res.status(401).json({ error: req.__("Not authorized") });
325
362
  }
326
363
  }
@@ -340,6 +377,7 @@ router.post(
340
377
  const { tableName } = req.params;
341
378
  const table = await Table.findOne({ name: tableName });
342
379
  if (!table) {
380
+ getState().log(3, `API POST ${tableName} not found`);
343
381
  res.status(404).json({ error: req.__("Not found") });
344
382
  return;
345
383
  }
@@ -378,6 +416,10 @@ router.post(
378
416
  }
379
417
  });
380
418
  if (hasErrors) {
419
+ getState().log(
420
+ 2,
421
+ `API POST ${table.name} error: ${errors.join(", ")}`
422
+ );
381
423
  res.status(400).json({ error: errors.join(", ") });
382
424
  return;
383
425
  }
@@ -385,9 +427,12 @@ router.post(
385
427
  row,
386
428
  req.user || user || { role_id: 10 }
387
429
  );
388
- if (ins_res.error) res.status(400).json(ins_res);
389
- else res.json(ins_res);
430
+ if (ins_res.error) {
431
+ getState().log(2, `API POST ${table.name} error: ${ins_res.error}`);
432
+ res.status(400).json(ins_res);
433
+ } else res.json(ins_res);
390
434
  } else {
435
+ getState().log(3, `API POST ${table.name} not authorized`);
391
436
  res.status(401).json({ error: req.__("Not authorized") });
392
437
  }
393
438
  }
@@ -408,6 +453,7 @@ router.post(
408
453
  const { tableName, id } = req.params;
409
454
  const table = await Table.findOne({ name: tableName });
410
455
  if (!table) {
456
+ getState().log(3, `API POST ${tableName} not found`);
411
457
  res.status(404).json({ error: req.__("Not found") });
412
458
  return;
413
459
  }
@@ -445,6 +491,10 @@ router.post(
445
491
  }
446
492
  }
447
493
  if (hasErrors) {
494
+ getState().log(
495
+ 2,
496
+ `API POST ${table.name} error: ${errors.join(", ")}`
497
+ );
448
498
  res.status(400).json({ error: errors.join(", ") });
449
499
  return;
450
500
  }
@@ -454,9 +504,12 @@ router.post(
454
504
  user || req.user || { role_id: 10 }
455
505
  );
456
506
 
457
- if (ins_res.error) res.status(400).json(ins_res);
458
- else res.json(ins_res);
507
+ if (ins_res.error) {
508
+ getState().log(2, `API POST ${table.name} error: ${ins_res.error}`);
509
+ res.status(400).json(ins_res);
510
+ } else res.json(ins_res);
459
511
  } else {
512
+ getState().log(3, `API POST ${table.name} not authorized`);
460
513
  res.status(401).json({ error: req.__("Not authorized") });
461
514
  }
462
515
  }
@@ -477,6 +530,7 @@ router.delete(
477
530
  const { tableName, id } = req.params;
478
531
  const table = await Table.findOne({ name: tableName });
479
532
  if (!table) {
533
+ getState().log(3, `API DELETE ${tableName} not found`);
480
534
  res.status(404).json({ error: req.__("Not found") });
481
535
  return;
482
536
  }
@@ -502,9 +556,11 @@ router.delete(
502
556
  );
503
557
  res.json({ success: true });
504
558
  } catch (e) {
559
+ getState().log(2, `API DELETE ${table.name} error: ${e.message}`);
505
560
  res.status(400).json({ error: e.message });
506
561
  }
507
562
  } else {
563
+ getState().log(3, `API DELETE ${table.name} not authorized`);
508
564
  res.status(401).json({ error: req.__("Not authorized") });
509
565
  }
510
566
  }
@@ -33,6 +33,7 @@ const tableBadges = (t, req) => {
33
33
  if (t.ownership_field_id) s += badge("primary", req.__("Owned"));
34
34
  if (t.versioned) s += badge("success", req.__("History"));
35
35
  if (t.external) s += badge("info", req.__("External"));
36
+ if (t.provider_name) s += badge("success", t.provider_name);
36
37
  return s;
37
38
  };
38
39
 
package/routes/fields.js CHANGED
@@ -398,9 +398,16 @@ const fieldFlow = (req) =>
398
398
  ];
399
399
  const keyfields = orderedFields
400
400
  .filter((f) => !f.calculated || f.stored)
401
+ .sort((a, b) =>
402
+ a.type?.name === "String" && b.type?.name !== "String"
403
+ ? -1
404
+ : a.type?.name !== "String" && b.type?.name === "String"
405
+ ? 1
406
+ : 0
407
+ )
401
408
  .map((f) => ({
402
409
  value: f.name,
403
- label: f.label,
410
+ label: `${f.label} [${f.type?.name || f.type}]`,
404
411
  }));
405
412
  const textfields = orderedFields
406
413
  .filter(
@@ -412,6 +419,9 @@ const fieldFlow = (req) =>
412
419
  new Field({
413
420
  name: "summary_field",
414
421
  label: req.__("Summary field"),
422
+ sublabel: req.__(
423
+ "The field that will be shown to the user when choosing a value"
424
+ ),
415
425
  input_type: "select",
416
426
  options: keyfields,
417
427
  }),
package/routes/index.js CHANGED
@@ -64,6 +64,7 @@ const edit = require("./edit");
64
64
  const config = require("./config");
65
65
  const viewedit = require("./viewedit");
66
66
  const crashlog = require("./crashlog");
67
+ const notifications = require("./notifications");
67
68
  const del = require("./delete");
68
69
  const auth = require("../auth/routes");
69
70
  const useradmin = require("../auth/admin");
@@ -96,6 +97,7 @@ module.exports =
96
97
  app.use("/actions", actions);
97
98
  app.use("/eventlog", eventlog);
98
99
  app.use("/library", library);
100
+ app.use("/notifications", notifications);
99
101
  app.use("/site-structure", infoarch);
100
102
  app.use("/search", search);
101
103
  app.use("/admin", admin);