@saltcorn/server 0.6.1-beta.3 → 0.6.2-beta.2

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/files.js CHANGED
@@ -8,6 +8,7 @@ const Router = require("express-promise-router");
8
8
  const File = require("@saltcorn/data/models/file");
9
9
  const User = require("@saltcorn/data/models/user");
10
10
  const { getState } = require("@saltcorn/data/db/state");
11
+ const s3storage = require("../s3storage");
11
12
 
12
13
  const {
13
14
  mkTable,
@@ -16,7 +17,7 @@ const {
16
17
  post_btn,
17
18
  post_delete_btn,
18
19
  } = require("@saltcorn/markup");
19
- const { setTenant, isAdmin, error_catcher } = require("./utils.js");
20
+ const { isAdmin, error_catcher } = require("./utils.js");
20
21
  const {
21
22
  span,
22
23
  h5,
@@ -37,6 +38,11 @@ const {
37
38
  const { csrfField } = require("./utils");
38
39
  const { editRoleForm, fileUploadForm } = require("../markup/forms.js");
39
40
  const { strictParseInt } = require("@saltcorn/data/plugin-helper");
41
+ const {
42
+ send_files_page,
43
+ config_fields_form,
44
+ save_config_from_form,
45
+ } = require("../markup/admin");
40
46
 
41
47
  /**
42
48
  * @type {object}
@@ -49,9 +55,9 @@ const router = new Router();
49
55
  module.exports = router;
50
56
 
51
57
  /**
52
- * @param {*} file
53
- * @param {*} roles
54
- * @param {*} req
58
+ * @param {*} file
59
+ * @param {*} roles
60
+ * @param {*} req
55
61
  * @returns {Form}
56
62
  */
57
63
  const editFileRoleForm = (file, roles, req) =>
@@ -70,61 +76,53 @@ const editFileRoleForm = (file, roles, req) =>
70
76
  */
71
77
  router.get(
72
78
  "/",
73
- setTenant,
74
79
  isAdmin,
75
80
  error_catcher(async (req, res) => {
76
81
  const rows = await File.find({}, { orderBy: "filename" });
77
82
  const roles = await User.get_roles();
78
- res.sendWrap("Files", {
79
- above: [
80
- {
81
- type: "breadcrumbs",
82
- crumbs: [
83
- { text: req.__("Settings"), href: "/settings" },
84
- { text: req.__("Files") },
85
- ],
86
- },
87
- {
88
- type: "card",
89
- contents: [
90
- mkTable(
91
- [
92
- {
93
- label: req.__("Filename"),
94
- key: (r) =>
95
- div(
96
- { "data-inline-edit-dest-url": `/files/setname/${r.id}` },
97
- r.filename
98
- ),
99
- },
100
- { label: req.__("Size (KiB)"), key: "size_kb", align: "right" },
101
- { label: req.__("Media type"), key: (r) => r.mimetype },
102
- {
103
- label: req.__("Role to access"),
104
- key: (r) => editFileRoleForm(r, roles, req),
105
- },
106
- {
107
- label: req.__("Link"),
108
- key: (r) => link(`/files/serve/${r.id}`, req.__("Link")),
109
- },
110
- {
111
- label: req.__("Download"),
112
- key: (r) =>
113
- link(`/files/download/${r.id}`, req.__("Download")),
114
- },
115
- {
116
- label: req.__("Delete"),
117
- key: (r) =>
118
- post_delete_btn(`/files/delete/${r.id}`, req, r.filename),
119
- },
120
- ],
121
- rows,
122
- { hover: true }
123
- ),
124
- fileUploadForm(req),
125
- ],
126
- },
127
- ],
83
+ send_files_page({
84
+ res,
85
+ req,
86
+ active_sub: "Files",
87
+ contents: {
88
+ type: "card",
89
+ contents: [
90
+ mkTable(
91
+ [
92
+ {
93
+ label: req.__("Filename"),
94
+ key: (r) =>
95
+ div(
96
+ { "data-inline-edit-dest-url": `/files/setname/${r.id}` },
97
+ r.filename
98
+ ),
99
+ },
100
+ { label: req.__("Size (KiB)"), key: "size_kb", align: "right" },
101
+ { label: req.__("Media type"), key: (r) => r.mimetype },
102
+ {
103
+ label: req.__("Role to access"),
104
+ key: (r) => editFileRoleForm(r, roles, req),
105
+ },
106
+ {
107
+ label: req.__("Link"),
108
+ key: (r) => link(`/files/serve/${r.id}`, req.__("Link")),
109
+ },
110
+ {
111
+ label: req.__("Download"),
112
+ key: (r) => link(`/files/download/${r.id}`, req.__("Download")),
113
+ },
114
+ {
115
+ label: req.__("Delete"),
116
+ key: (r) =>
117
+ post_delete_btn(`/files/delete/${r.id}`, req, r.filename),
118
+ },
119
+ ],
120
+ rows,
121
+ { hover: true }
122
+ ),
123
+ fileUploadForm(req),
124
+ ],
125
+ },
128
126
  });
129
127
  })
130
128
  );
@@ -137,7 +135,6 @@ router.get(
137
135
  */
138
136
  router.get(
139
137
  "/download/:id",
140
- setTenant,
141
138
  error_catcher(async (req, res) => {
142
139
  const role = req.isAuthenticated() ? req.user.role_id : 10;
143
140
  const user_id = req.user && req.user.id;
@@ -145,7 +142,8 @@ router.get(
145
142
  const file = await File.findOne({ id });
146
143
  if (role <= file.min_role_read || (user_id && user_id === file.user_id)) {
147
144
  res.type(file.mimetype);
148
- res.download(file.location, file.filename);
145
+ if (file.s3_store) s3storage.serveObject(file, res, true);
146
+ else res.download(file.location, file.filename);
149
147
  } else {
150
148
  req.flash("warning", req.__("Not authorized"));
151
149
  res.redirect("/");
@@ -161,7 +159,6 @@ router.get(
161
159
  */
162
160
  router.get(
163
161
  "/serve/:id",
164
- setTenant,
165
162
  error_catcher(async (req, res) => {
166
163
  const role = req.isAuthenticated() ? req.user.role_id : 10;
167
164
  const user_id = req.user && req.user.id;
@@ -181,7 +178,8 @@ router.get(
181
178
  res.type(file.mimetype);
182
179
  const cacheability = file.min_role_read === 10 ? "public" : "private";
183
180
  res.set("Cache-Control", `${cacheability}, max-age=86400`);
184
- res.sendFile(file.location);
181
+ if (file.s3_store) s3storage.serveObject(file, res, false);
182
+ else res.sendFile(file.location);
185
183
  } else {
186
184
  req.flash("warning", req.__("Not authorized"));
187
185
  res.redirect("/");
@@ -197,7 +195,6 @@ router.get(
197
195
  */
198
196
  router.post(
199
197
  "/setrole/:id",
200
- setTenant,
201
198
  isAdmin,
202
199
  error_catcher(async (req, res) => {
203
200
  const { id } = req.params;
@@ -225,7 +222,6 @@ router.post(
225
222
  */
226
223
  router.post(
227
224
  "/setname/:id",
228
- setTenant,
229
225
  isAdmin,
230
226
  error_catcher(async (req, res) => {
231
227
  const { id } = req.params;
@@ -244,7 +240,6 @@ router.post(
244
240
  */
245
241
  router.post(
246
242
  "/upload",
247
- setTenant,
248
243
  error_catcher(async (req, res) => {
249
244
  let jsonResp = {};
250
245
  const min_role_upload = getState().getConfig("min_role_upload", 1);
@@ -297,12 +292,18 @@ router.post(
297
292
  */
298
293
  router.post(
299
294
  "/delete/:id",
300
- setTenant,
301
295
  isAdmin,
302
296
  error_catcher(async (req, res) => {
303
297
  const { id } = req.params;
304
298
  const f = await File.findOne({ id });
305
- const result = await f.delete();
299
+ if (!f) {
300
+ req.flash("error", "File not found");
301
+ res.redirect("/files");
302
+ return;
303
+ }
304
+ const result = await f.delete(
305
+ f.s3_store ? s3storage.unlinkObject : undefined
306
+ );
306
307
  if (result && result.error) {
307
308
  req.flash("error", result.error);
308
309
  } else {
@@ -311,3 +312,82 @@ router.post(
311
312
  res.redirect(`/files`);
312
313
  })
313
314
  );
315
+
316
+ /**
317
+ * Storage settings form definition
318
+ * @param {object} req request
319
+ * @returns {Promise<Form>} form
320
+ */
321
+ const storage_form = async (req) => {
322
+ const form = await config_fields_form({
323
+ req,
324
+ field_names: [
325
+ "storage_s3_enabled",
326
+ "storage_s3_bucket",
327
+ "storage_s3_path_prefix",
328
+ "storage_s3_endpoint",
329
+ "storage_s3_region",
330
+ "storage_s3_access_key",
331
+ "storage_s3_access_secret",
332
+ "storage_s3_secure",
333
+ ],
334
+ action: "/files/storage",
335
+ });
336
+ form.submitButtonClass = "btn-outline-primary";
337
+ form.submitLabel = req.__("Save");
338
+ form.onChange = "remove_outline(this)";
339
+ return form;
340
+ };
341
+
342
+ /**
343
+ * @name get/storage
344
+ * @function
345
+ * @memberof module:routes/admin~routes/adminRouter
346
+ */
347
+ router.get(
348
+ "/storage",
349
+ isAdmin,
350
+ error_catcher(async (req, res) => {
351
+ const form = await storage_form(req);
352
+ send_files_page({
353
+ res,
354
+ req,
355
+ active_sub: "Storage",
356
+ contents: {
357
+ type: "card",
358
+ title: req.__("Storage settings"),
359
+ contents: [renderForm(form, req.csrfToken())],
360
+ },
361
+ });
362
+ })
363
+ );
364
+
365
+ /**
366
+ * @name post/email
367
+ * @function
368
+ * @memberof module:routes/admin~routes/adminRouter
369
+ */
370
+ router.post(
371
+ "/storage",
372
+ isAdmin,
373
+ error_catcher(async (req, res) => {
374
+ const form = await storage_form(req);
375
+ form.validate(req.body);
376
+ if (form.hasErrors) {
377
+ send_admin_page({
378
+ res,
379
+ req,
380
+ active_sub: "Storage",
381
+ contents: {
382
+ type: "card",
383
+ title: req.__("Storage settings"),
384
+ contents: [renderForm(form, req.csrfToken())],
385
+ },
386
+ });
387
+ } else {
388
+ await save_config_from_form(form);
389
+ req.flash("success", req.__("Storage settings updated"));
390
+ res.redirect("/files/storage");
391
+ }
392
+ })
393
+ );
@@ -19,10 +19,11 @@ const { get_latest_npm_version } = require("@saltcorn/data/models/config");
19
19
  const packagejson = require("../package.json");
20
20
  const Trigger = require("@saltcorn/data/models/trigger");
21
21
  const { fileUploadForm } = require("../markup/forms");
22
+ const { get_base_url } = require("./utils.js");
22
23
 
23
24
  /**
24
- * @param {*} tables
25
- * @param {object} req
25
+ * @param {*} tables
26
+ * @param {object} req
26
27
  * @returns {Table}
27
28
  */
28
29
  const tableTable = (tables, req) =>
@@ -38,8 +39,8 @@ const tableTable = (tables, req) =>
38
39
  );
39
40
 
40
41
  /**
41
- * @param {*} tables
42
- * @param {object} req
42
+ * @param {*} tables
43
+ * @param {object} req
43
44
  * @returns {object}
44
45
  */
45
46
  const tableCard = (tables, req) => ({
@@ -67,8 +68,8 @@ const tableCard = (tables, req) => ({
67
68
  });
68
69
 
69
70
  /**
70
- * @param {*} views
71
- * @param {object} req
71
+ * @param {*} views
72
+ * @param {object} req
72
73
  * @returns {Table}
73
74
  */
74
75
  const viewTable = (views, req) =>
@@ -88,8 +89,8 @@ const viewTable = (views, req) =>
88
89
  );
89
90
 
90
91
  /**
91
- * @param {*} views
92
- * @param {object} req
92
+ * @param {*} views
93
+ * @param {object} req
93
94
  * @returns {object}
94
95
  */
95
96
  const viewCard = (views, req) => ({
@@ -119,8 +120,8 @@ const viewCard = (views, req) => ({
119
120
  });
120
121
 
121
122
  /**
122
- * @param {*} pages
123
- * @param {object} req
123
+ * @param {*} pages
124
+ * @param {object} req
124
125
  * @returns {Table}
125
126
  */
126
127
  const pageTable = (pages, req) =>
@@ -140,8 +141,8 @@ const pageTable = (pages, req) =>
140
141
  );
141
142
 
142
143
  /**
143
- * @param {*} pages
144
- * @param {object} req
144
+ * @param {*} pages
145
+ * @param {object} req
145
146
  * @returns {object}
146
147
  */
147
148
  const pageCard = (pages, req) => ({
@@ -172,11 +173,11 @@ const pageCard = (pages, req) => ({
172
173
  });
173
174
 
174
175
  /**
175
- * @param {object} req
176
+ * @param {object} req
176
177
  * @returns {Promise<div>}
177
178
  */
178
179
  const filesTab = async (req) => {
179
- const files = await File.find({}, { orderBy: "filename" });
180
+ const files = await File.find({}, { orderBy: "filename", cached: true });
180
181
  return div(
181
182
  files.length == 0
182
183
  ? p(req.__("No files"))
@@ -196,16 +197,10 @@ const filesTab = async (req) => {
196
197
  };
197
198
 
198
199
  /**
199
- * @param {object} req
200
+ * @param {object} req
200
201
  * @returns {Promise<div>}
201
202
  */
202
- const usersTab = async (req) => {
203
- const users = await User.find({}, { orderBy: "id" });
204
- const roles = await User.get_roles();
205
- var roleMap = {};
206
- roles.forEach((r) => {
207
- roleMap[r.id] = r.role;
208
- });
203
+ const usersTab = async (req, users, roleMap) => {
209
204
  return div(
210
205
  mkTable(
211
206
  [
@@ -219,20 +214,21 @@ const usersTab = async (req) => {
219
214
  users
220
215
  ),
221
216
  a(
222
- { href: `/useradmin/new`, class: "btn btn-secondary" },
217
+ { href: `/useradmin/new`, class: "btn btn-secondary my-3" },
223
218
  req.__("Create user")
224
219
  )
225
220
  );
226
221
  };
227
222
 
228
223
  /**
229
- * @param {object} req
224
+ * @param {object} req
230
225
  * @returns {Promise<div>}
231
226
  */
232
- const actionsTab = async (req) => {
233
- const triggers = await Trigger.findAllWithTableName();
227
+ const actionsTab = async (req, triggers) => {
228
+ const base_url = get_base_url(req);
234
229
 
235
230
  return div(
231
+ { class: "pb-3" },
236
232
  triggers.length <= 1 &&
237
233
  p(
238
234
  { class: "mt-2 pr-2" },
@@ -259,14 +255,73 @@ const actionsTab = async (req) => {
259
255
  triggers
260
256
  ),
261
257
  a(
262
- { href: "/actions/new", class: "btn btn-secondary btn-smj" },
258
+ { href: "/actions/new", class: "btn btn-secondary my-3" },
263
259
  req.__("Add trigger")
264
260
  )
265
261
  );
266
262
  };
263
+ const packTab = (req, packlist) =>
264
+ div(
265
+ { class: "pb-3 pt-2 pr-4" },
266
+ p(req.__("Instead of building, get up and running in no time with packs")),
267
+ p(
268
+ { class: "font-italic" },
269
+ req.__(
270
+ "Packs are collections of tables, views and plugins that give you a full application which you can then edit to suit your needs."
271
+ )
272
+ ),
273
+ mkTable(
274
+ [
275
+ { label: req.__("Name"), key: "name" },
276
+ {
277
+ label: req.__("Description"),
278
+ key: "description",
279
+ },
280
+ ],
281
+ packlist,
282
+ { noHeader: true }
283
+ ),
284
+ a(
285
+ { href: `/plugins?set=packs`, class: "btn btn-primary" },
286
+ req.__("Go to pack store »")
287
+ )
288
+ );
289
+
290
+ const helpCard = (req) =>
291
+ div(
292
+ { class: "pb-3 pt-2 pr-4" },
293
+ p(req.__("Confused?")),
294
+ p(
295
+ req.__(
296
+ "The Wiki contains the documentation and tutorials on installing and using Saltcorn"
297
+ )
298
+ ),
299
+ a(
300
+ {
301
+ href: `https://wiki.saltcorn.com/`,
302
+ class: "btn btn-primary",
303
+ },
304
+ req.__("Go to Wiki »")
305
+ ),
306
+ p(req.__("The YouTube channel has some video tutorials")),
307
+ a(
308
+ {
309
+ href: `https://www.youtube.com/channel/UCBOpAcH8ep7ESbuocxcq0KQ`,
310
+ class: "btn btn-secondary",
311
+ },
312
+ req.__("Go to YouTube »")
313
+ ),
314
+ div(
315
+ { class: "mt-3" },
316
+ a(
317
+ { href: `https://blog.saltcorn.com/` },
318
+ req.__("What's new? Read the blog »")
319
+ )
320
+ )
321
+ );
267
322
 
268
323
  /**
269
- * @param {object} req
324
+ * @param {object} req
270
325
  * @returns {Promise<object>}
271
326
  */
272
327
  const welcome_page = async (req) => {
@@ -275,10 +330,16 @@ const welcome_page = async (req) => {
275
330
  ...packs_available.slice(0, 5),
276
331
  { name: req.__("More..."), description: "" },
277
332
  ];
278
- const tables = await Table.find({}, { orderBy: "name" });
279
- const views = await View.find({});
280
- const pages = await Page.find({});
281
-
333
+ const tables = await Table.find({}, { cached: true });
334
+ const views = await View.find({}, { cached: true });
335
+ const pages = await Page.find({}, { cached: true });
336
+ const triggers = await Trigger.findAllWithTableName();
337
+ const users = await User.find({}, { orderBy: "id" });
338
+ const roles = await User.get_roles();
339
+ let roleMap = {};
340
+ roles.forEach((r) => {
341
+ roleMap[r.id] = r.role;
342
+ });
282
343
  return {
283
344
  above: [
284
345
  {
@@ -293,75 +354,37 @@ const welcome_page = async (req) => {
293
354
  {
294
355
  type: "card",
295
356
  //title: req.__("Install pack"),
296
- tabContents: {
297
- Packs: div(
298
- p(
299
- req.__(
300
- "Instead of building, get up and running in no time with packs"
301
- )
302
- ),
303
- p(
304
- { class: "font-italic" },
305
- req.__(
306
- "Packs are collections of tables, views and plugins that give you a full application which you can then edit to suit your needs."
307
- )
308
- ),
309
- mkTable(
310
- [
311
- { label: req.__("Name"), key: "name" },
312
- {
313
- label: req.__("Description"),
314
- key: "description",
315
- },
316
- ],
317
- packlist,
318
- { noHeader: true }
319
- ),
320
- a(
321
- { href: `/plugins?set=packs`, class: "btn btn-primary" },
322
- req.__("Go to pack store »")
323
- )
324
- ),
325
- Triggers: await actionsTab(req),
326
- Files: await filesTab(req),
327
- },
357
+ bodyClass: "py-0 pr-0",
358
+ class: "welcome-page-entity-list",
359
+
360
+ tabContents:
361
+ triggers.length > 0
362
+ ? {
363
+ Triggers: await actionsTab(req, triggers),
364
+ Files: await filesTab(req),
365
+ Packs: packTab(req, packlist),
366
+ }
367
+ : {
368
+ Packs: packTab(req, packlist),
369
+ Triggers: await actionsTab(req, triggers),
370
+ Files: await filesTab(req),
371
+ },
328
372
  },
329
373
  {
330
374
  type: "card",
331
375
  //title: req.__("Learn"),
332
- tabContents: {
333
- Help: div(
334
- p(req.__("Confused?")),
335
- p(
336
- req.__(
337
- "The Wiki contains the documentation and tutorials on installing and using Saltcorn"
338
- )
339
- ),
340
- a(
341
- {
342
- href: `https://wiki.saltcorn.com/`,
343
- class: "btn btn-primary",
344
- },
345
- req.__("Go to Wiki »")
346
- ),
347
- p(req.__("The YouTube channel has some video tutorials")),
348
- a(
349
- {
350
- href: `https://www.youtube.com/channel/UCBOpAcH8ep7ESbuocxcq0KQ`,
351
- class: "btn btn-secondary",
376
+ bodyClass: "py-0 pr-0",
377
+ class: "welcome-page-entity-list",
378
+ tabContents:
379
+ users.length > 4
380
+ ? {
381
+ Users: await usersTab(req, users, roleMap),
382
+ Help: helpCard(req),
383
+ }
384
+ : {
385
+ Help: helpCard(req),
386
+ Users: await usersTab(req, users, roleMap),
352
387
  },
353
- req.__("Go to YouTube »")
354
- ),
355
- div(
356
- { class: "mt-3" },
357
- a(
358
- { href: `https://blog.saltcorn.com/` },
359
- req.__("What's new? Read the blog »")
360
- )
361
- )
362
- ),
363
- Users: await usersTab(req),
364
- },
365
388
  },
366
389
  ],
367
390
  },
@@ -370,8 +393,8 @@ const welcome_page = async (req) => {
370
393
  };
371
394
 
372
395
  /**
373
- * @param {object} req
374
- * @param {object} res
396
+ * @param {object} req
397
+ * @param {object} res
375
398
  * @returns {Promise<void>}
376
399
  */
377
400
  const no_views_logged_in = async (req, res) => {
@@ -400,9 +423,9 @@ const no_views_logged_in = async (req, res) => {
400
423
  };
401
424
 
402
425
  /**
403
- * @param {number} role_id
404
- * @param {object} res
405
- * @param {object} req
426
+ * @param {number} role_id
427
+ * @param {object} res
428
+ * @param {object} req
406
429
  * @returns {Promise<boolean>}
407
430
  */
408
431
  const get_config_response = async (role_id, res, req) => {
@@ -420,8 +443,11 @@ const get_config_response = async (role_id, res, req) => {
420
443
  const contents = await db_page.run(req.query, { res, req });
421
444
 
422
445
  res.sendWrap(
423
- { title: db_page.title, description: db_page.description } ||
424
- `${pagename} page`,
446
+ {
447
+ title: db_page.title,
448
+ description: db_page.description,
449
+ bodyClass: "page_" + db.sqlsanitize(homeCfg),
450
+ } || `${pagename} page`,
425
451
  contents
426
452
  );
427
453
  } else res.redirect(homeCfg);
@@ -431,8 +457,8 @@ const get_config_response = async (role_id, res, req) => {
431
457
 
432
458
  /**
433
459
  * Function assigned to 'module.exports'.
434
- * @param {object} req
435
- * @param {object} res
460
+ * @param {object} req
461
+ * @param {object} res
436
462
  * @returns {Promise<void>}
437
463
  */
438
464
  module.exports = async (req, res) => {