@saltcorn/server 0.6.2-beta.1 → 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/tenant.js CHANGED
@@ -35,7 +35,7 @@ const {
35
35
  const db = require("@saltcorn/data/db");
36
36
  const url = require("url");
37
37
  const { loadAllPlugins, loadAndSaveNewPlugin } = require("../load_plugins");
38
- const { setTenant, isAdmin, error_catcher } = require("./utils.js");
38
+ const { isAdmin, error_catcher } = require("./utils.js");
39
39
  const User = require("@saltcorn/data/models/user");
40
40
  const File = require("@saltcorn/data/models/file");
41
41
  const {
@@ -115,7 +115,6 @@ const is_ip_address = (hostname) => {
115
115
  */
116
116
  router.get(
117
117
  "/create",
118
- setTenant,
119
118
  error_catcher(async (req, res) => {
120
119
  if (
121
120
  !db.is_it_multi_tenant() ||
@@ -207,7 +206,6 @@ const getNewURL = (req, subdomain) => {
207
206
  */
208
207
  router.post(
209
208
  "/create",
210
- setTenant,
211
209
  error_catcher(async (req, res) => {
212
210
  // check that multi-tenancy is enabled
213
211
  if (
@@ -306,7 +304,6 @@ router.post(
306
304
  */
307
305
  router.get(
308
306
  "/list",
309
- setTenant,
310
307
  isAdmin,
311
308
  error_catcher(async (req, res) => {
312
309
  if (
@@ -390,7 +387,6 @@ const tenant_settings_form = (req) =>
390
387
  */
391
388
  router.get(
392
389
  "/settings",
393
- setTenant,
394
390
  isAdmin,
395
391
  error_catcher(async (req, res) => {
396
392
  if (
@@ -425,7 +421,6 @@ router.get(
425
421
  */
426
422
  router.post(
427
423
  "/settings",
428
- setTenant,
429
424
  isAdmin,
430
425
  error_catcher(async (req, res) => {
431
426
  const form = await tenant_settings_form(req);
@@ -502,7 +497,6 @@ const get_tenant_info = async (subdomain) => {
502
497
  */
503
498
  router.get(
504
499
  "/info/:subdomain",
505
- setTenant,
506
500
  isAdmin,
507
501
  error_catcher(async (req, res) => {
508
502
  if (
@@ -648,7 +642,6 @@ router.get(
648
642
  */
649
643
  router.post(
650
644
  "/info/:subdomain",
651
- setTenant,
652
645
  isAdmin,
653
646
  error_catcher(async (req, res) => {
654
647
  if (
@@ -680,7 +673,6 @@ router.post(
680
673
  */
681
674
  router.post(
682
675
  "/delete/:sub",
683
- setTenant,
684
676
  isAdmin,
685
677
  error_catcher(async (req, res) => {
686
678
  if (
package/routes/utils.js CHANGED
@@ -20,9 +20,9 @@ const { validateHeaderName, validateHeaderValue } = require("http");
20
20
  const Crash = require("@saltcorn/data/models/crash");
21
21
 
22
22
  /**
23
- * @param {object} req
24
- * @param {object} res
25
- * @param {function} next
23
+ * @param {object} req
24
+ * @param {object} res
25
+ * @param {function} next
26
26
  * @returns {void}
27
27
  */
28
28
  function loggedIn(req, res, next) {
@@ -35,9 +35,9 @@ function loggedIn(req, res, next) {
35
35
  }
36
36
 
37
37
  /**
38
- * @param {object} req
39
- * @param {object} res
40
- * @param {function} next
38
+ * @param {object} req
39
+ * @param {object} res
40
+ * @param {function} next
41
41
  * @returns {void}
42
42
  */
43
43
  function isAdmin(req, res, next) {
@@ -54,8 +54,8 @@ function isAdmin(req, res, next) {
54
54
  }
55
55
 
56
56
  /**
57
- * @param {object} req
58
- * @param {object} res
57
+ * @param {object} req
58
+ * @param {object} res
59
59
  * @param {string} state
60
60
  * @returns {void}
61
61
  */
@@ -67,8 +67,8 @@ const setLanguage = (req, res, state) => {
67
67
  };
68
68
 
69
69
  /**
70
- * @param {object} res
71
- * @param {string} state
70
+ * @param {object} res
71
+ * @param {string} state
72
72
  * @returns {void}
73
73
  */
74
74
  const set_custom_http_headers = (res, state) => {
@@ -90,7 +90,7 @@ const set_custom_http_headers = (res, state) => {
90
90
  };
91
91
 
92
92
  /**
93
- * @param {object} req
93
+ * @param {object} req
94
94
  * @returns {string}
95
95
  */
96
96
  const get_tenant_from_req = (req) => {
@@ -106,9 +106,9 @@ const get_tenant_from_req = (req) => {
106
106
  };
107
107
 
108
108
  /**
109
- * @param {object} req
110
- * @param {object} res
111
- * @param {function} next
109
+ * @param {object} req
110
+ * @param {object} res
111
+ * @param {function} next
112
112
  */
113
113
  const setTenant = (req, res, next) => {
114
114
  if (db.is_it_multi_tenant()) {
@@ -141,7 +141,7 @@ const setTenant = (req, res, next) => {
141
141
  };
142
142
 
143
143
  /**
144
- * @param {object} req
144
+ * @param {object} req
145
145
  * @returns {input}
146
146
  */
147
147
  const csrfField = (req) =>
@@ -152,7 +152,7 @@ const csrfField = (req) =>
152
152
  });
153
153
 
154
154
  /**
155
- * @param {function} fn
155
+ * @param {function} fn
156
156
  * @returns {function}
157
157
  */
158
158
  const error_catcher = (fn) => (request, response, next) => {
@@ -160,8 +160,8 @@ const error_catcher = (fn) => (request, response, next) => {
160
160
  };
161
161
 
162
162
  /**
163
- * @param {string|object} contents
164
- * @param {string} viewname
163
+ * @param {string|object} contents
164
+ * @param {string} viewname
165
165
  * @returns {string}
166
166
  */
167
167
  const scan_for_page_title = (contents, viewname) => {
@@ -224,10 +224,10 @@ module.exports = {
224
224
  csrfField,
225
225
  loggedIn,
226
226
  isAdmin,
227
- setTenant,
228
227
  get_base_url,
229
228
  error_catcher,
230
229
  scan_for_page_title,
231
230
  getGitRevision,
232
231
  getSessionStore,
232
+ setTenant,
233
233
  };
package/routes/view.js CHANGED
@@ -13,12 +13,12 @@ const Page = require("@saltcorn/data/models/page");
13
13
  const { div, text, i, a } = require("@saltcorn/markup/tags");
14
14
  const { renderForm, link } = require("@saltcorn/markup");
15
15
  const {
16
- setTenant,
17
16
  isAdmin,
18
17
  error_catcher,
19
18
  scan_for_page_title,
20
19
  } = require("../routes/utils.js");
21
20
  const { add_edit_bar } = require("../markup/admin.js");
21
+ const { InvalidConfiguration } = require("@saltcorn/data/utils");
22
22
 
23
23
  /**
24
24
  * @type {object}
@@ -38,7 +38,6 @@ module.exports = router;
38
38
  */
39
39
  router.get(
40
40
  "/:viewname",
41
- setTenant,
42
41
  error_catcher(async (req, res) => {
43
42
  const { viewname } = req.params;
44
43
 
@@ -82,7 +81,6 @@ router.get(
82
81
  */
83
82
  router.post(
84
83
  "/:viewname/preview",
85
- setTenant,
86
84
  isAdmin,
87
85
  error_catcher(async (req, res) => {
88
86
  const { viewname } = req.params;
@@ -120,7 +118,6 @@ router.post(
120
118
  */
121
119
  router.post(
122
120
  "/:viewname/:route",
123
- setTenant,
124
121
  error_catcher(async (req, res) => {
125
122
  const { viewname, route } = req.params;
126
123
  const role = req.isAuthenticated() ? req.user.role_id : 10;
@@ -146,7 +143,6 @@ router.post(
146
143
  */
147
144
  router.post(
148
145
  "/:viewname",
149
- setTenant,
150
146
  error_catcher(async (req, res) => {
151
147
  const { viewname } = req.params;
152
148
  const role = req.isAuthenticated() ? req.user.role_id : 10;
@@ -161,6 +157,12 @@ router.post(
161
157
  ) {
162
158
  req.flash("danger", req.__("Not authorized"));
163
159
  res.redirect("/");
160
+ } else if (!view.runPost) {
161
+ throw new InvalidConfiguration(
162
+ `View ${text(viewname)} with template ${
163
+ view.viewtemplate
164
+ } does not supply a POST handler`
165
+ );
164
166
  } else {
165
167
  await view.runPost(req.query, req.body, { res, req });
166
168
  }
@@ -30,7 +30,7 @@ const {
30
30
  } = require("@saltcorn/markup/tags");
31
31
 
32
32
  const { getState } = require("@saltcorn/data/db/state");
33
- const { setTenant, isAdmin, error_catcher } = require("./utils.js");
33
+ const { isAdmin, error_catcher } = require("./utils.js");
34
34
  const Form = require("@saltcorn/data/models/form");
35
35
  const Field = require("@saltcorn/data/models/field");
36
36
  const Table = require("@saltcorn/data/models/table");
@@ -53,9 +53,9 @@ const router = new Router();
53
53
  module.exports = router;
54
54
 
55
55
  /**
56
- * @param {object} view
57
- * @param {object[]} roles
58
- * @param {object} req
56
+ * @param {object} view
57
+ * @param {object[]} roles
58
+ * @param {object} req
59
59
  * @returns {Form}
60
60
  */
61
61
  const editViewRoleForm = (view, roles, req) =>
@@ -67,8 +67,8 @@ const editViewRoleForm = (view, roles, req) =>
67
67
  });
68
68
 
69
69
  /**
70
- * @param {object} view
71
- * @param {object} req
70
+ * @param {object} view
71
+ * @param {object} req
72
72
  * @returns {div}
73
73
  */
74
74
  const view_dropdown = (view, req) =>
@@ -115,7 +115,6 @@ const view_dropdown = (view, req) =>
115
115
  */
116
116
  router.get(
117
117
  "/",
118
- setTenant,
119
118
  isAdmin,
120
119
  error_catcher(async (req, res) => {
121
120
  var orderBy = "name";
@@ -218,19 +217,19 @@ router.get(
218
217
  );
219
218
 
220
219
  /**
221
- * @param {object} o
222
- * @param {function} f
220
+ * @param {object} o
221
+ * @param {function} f
223
222
  * @returns {object}
224
223
  */
225
224
  const mapObjectValues = (o, f) =>
226
225
  Object.fromEntries(Object.entries(o).map(([k, v]) => [k, f(v)]));
227
226
 
228
227
  /**
229
- * @param {object} req
230
- * @param {object} tableOptions
231
- * @param {object[]} roles
232
- * @param {object[]} pages
233
- * @param {object} values
228
+ * @param {object} req
229
+ * @param {object} tableOptions
230
+ * @param {object[]} roles
231
+ * @param {object[]} pages
232
+ * @param {object} values
234
233
  * @returns {Form}
235
234
  */
236
235
  const viewForm = (req, tableOptions, roles, pages, values) => {
@@ -328,7 +327,6 @@ const viewForm = (req, tableOptions, roles, pages, values) => {
328
327
  */
329
328
  router.get(
330
329
  "/edit/:viewname",
331
- setTenant,
332
330
  isAdmin,
333
331
  error_catcher(async (req, res) => {
334
332
  const { viewname } = req.params;
@@ -376,7 +374,6 @@ router.get(
376
374
  */
377
375
  router.get(
378
376
  "/new",
379
- setTenant,
380
377
  isAdmin,
381
378
  error_catcher(async (req, res) => {
382
379
  const tables = await Table.find_with_external();
@@ -414,7 +411,6 @@ router.get(
414
411
  */
415
412
  router.post(
416
413
  "/save",
417
- setTenant,
418
414
  isAdmin,
419
415
  error_catcher(async (req, res) => {
420
416
  const tables = await Table.find_with_external();
@@ -484,11 +480,11 @@ router.post(
484
480
  );
485
481
 
486
482
  /**
487
- * @param {object} view
488
- * @param {Workflow} wf
489
- * @param {object} wfres
490
- * @param {object} req
491
- * @param {object} res
483
+ * @param {object} view
484
+ * @param {Workflow} wf
485
+ * @param {object} wfres
486
+ * @param {object} req
487
+ * @param {object} res
492
488
  * @returns {void}
493
489
  */
494
490
  const respondWorkflow = (view, wf, wfres, req, res) => {
@@ -532,7 +528,6 @@ const respondWorkflow = (view, wf, wfres, req, res) => {
532
528
  */
533
529
  router.get(
534
530
  "/config/:name",
535
- setTenant,
536
531
  isAdmin,
537
532
  error_catcher(async (req, res) => {
538
533
  const { name } = req.params;
@@ -565,7 +560,6 @@ router.get(
565
560
  */
566
561
  router.post(
567
562
  "/config/:name",
568
- setTenant,
569
563
  isAdmin,
570
564
  error_catcher(async (req, res) => {
571
565
  const { name } = req.params;
@@ -585,7 +579,6 @@ router.post(
585
579
  */
586
580
  router.post(
587
581
  "/add-to-menu/:id",
588
- setTenant,
589
582
  isAdmin,
590
583
  error_catcher(async (req, res) => {
591
584
  const { id } = req.params;
@@ -615,7 +608,6 @@ router.post(
615
608
  */
616
609
  router.post(
617
610
  "/clone/:id",
618
- setTenant,
619
611
  isAdmin,
620
612
  error_catcher(async (req, res) => {
621
613
  const { id } = req.params;
@@ -637,7 +629,6 @@ router.post(
637
629
  */
638
630
  router.post(
639
631
  "/delete/:id",
640
- setTenant,
641
632
  isAdmin,
642
633
  error_catcher(async (req, res) => {
643
634
  const { id } = req.params;
@@ -655,7 +646,6 @@ router.post(
655
646
  */
656
647
  router.post(
657
648
  "/savebuilder/:id",
658
- setTenant,
659
649
  isAdmin,
660
650
  error_catcher(async (req, res) => {
661
651
  const { id } = req.params;
@@ -679,7 +669,6 @@ router.post(
679
669
  */
680
670
  router.post(
681
671
  "/setrole/:id",
682
- setTenant,
683
672
  isAdmin,
684
673
  error_catcher(async (req, res) => {
685
674
  const { id } = req.params;
package/s3storage.js ADDED
@@ -0,0 +1,167 @@
1
+ var aws = require("aws-sdk");
2
+ var express = require("express");
3
+ var multer = require("multer");
4
+ var multerS3 = require("multer-s3");
5
+ const { getState } = require("@saltcorn/data/db/state");
6
+ const fileUpload = require("express-fileupload");
7
+ const { v4: uuidv4 } = require("uuid");
8
+ const { create } = require("@saltcorn/data/models/file");
9
+ var contentDisposition = require("content-disposition");
10
+
11
+ function createS3Client() {
12
+ return new aws.S3({
13
+ secretAccessKey: getState().getConfig("storage_s3_access_secret"),
14
+ accessKeyId: getState().getConfig("storage_s3_access_key"),
15
+ region: getState().getConfig("storage_s3_region"),
16
+ endpoint: getState().getConfig("storage_s3_endpoint"),
17
+ });
18
+ }
19
+
20
+ module.exports = {
21
+ /**
22
+ * Selector for file upload handler middleware. It will dispatch the
23
+ * file upload handler to the engine specified in the configuration.
24
+ *
25
+ * @param {*} req
26
+ * @param {*} res
27
+ * @param {*} next
28
+ */
29
+ middlewareSelect: async function (req, res, next) {
30
+ const useS3 = getState().getConfig("storage_s3_enabled");
31
+ if (useS3 === true) {
32
+ // Create S3 object
33
+
34
+ // Create multer function
35
+ const s3upload = multer({
36
+ storage: multerS3({
37
+ s3: createS3Client(),
38
+ bucket: getState().getConfig("storage_s3_bucket"),
39
+ metadata: function (req, file, cb) {
40
+ cb(null, { fieldName: file.fieldname });
41
+ },
42
+ key: function (req, file, cb) {
43
+ cb(null, "tmp/" + Date.now().toString() + uuidv4());
44
+ },
45
+ }),
46
+ }).any();
47
+
48
+ s3upload(req, res, next);
49
+ } else {
50
+ // Use regular file upload
51
+ fileUpload({
52
+ useTempFiles: true,
53
+ createParentPath: true,
54
+ tempFileDir: "/tmp/",
55
+ })(req, res, next);
56
+ }
57
+ },
58
+
59
+ /**
60
+ * Transform the processed req.files into that is suitable with
61
+ * Saltcorn File interface. It must be run after the middlewareSelect
62
+ *
63
+ * @param {*} req
64
+ * @param {*} res
65
+ * @param {*} next
66
+ */
67
+ middlewareTransform: async function (req, res, next) {
68
+ // If nothing to process or S3 is not enabled
69
+ const useS3 = getState().getConfig("storage_s3_enabled");
70
+ if (!req.files || !useS3) {
71
+ next();
72
+ return;
73
+ }
74
+
75
+ // Create S3 object
76
+ var s3 = createS3Client();
77
+ const bucket = getState().getConfig("storage_s3_bucket");
78
+
79
+ let newFileObject = {};
80
+ for (const file of req.files) {
81
+ file.mv = (newpath) => {
82
+ return new Promise((resolve, reject) => {
83
+ s3.copyObject(
84
+ {
85
+ Bucket: bucket,
86
+ CopySource: bucket + "/" + file.key,
87
+ Key: newpath,
88
+ },
89
+ function (err, data) {
90
+ if (err) reject(err);
91
+ else {
92
+ // Then delete
93
+ s3.deleteObject(
94
+ {
95
+ Bucket: bucket,
96
+ Key: file.key,
97
+ },
98
+ (err, data) => {
99
+ if (err) reject(err);
100
+ else resolve(data);
101
+ }
102
+ );
103
+ }
104
+ }
105
+ );
106
+ });
107
+ };
108
+ file.s3object = true;
109
+ file.name = file.originalname;
110
+ newFileObject[file.fieldname] = file;
111
+ }
112
+ req.files = newFileObject;
113
+ next();
114
+ },
115
+
116
+ /**
117
+ * Selector to serve object based on S3 state
118
+ *
119
+ * @param {*} file
120
+ * @param {*} res
121
+ * @param {*} download
122
+ */
123
+ serveObject: function (file, res, download) {
124
+ if (file.s3_store) {
125
+ var s3 = createS3Client();
126
+ const bucket = getState().getConfig("storage_s3_bucket");
127
+
128
+ var params = {
129
+ Bucket: bucket,
130
+ Key: file.location,
131
+ };
132
+
133
+ // Forward the object
134
+ s3.getObject(params)
135
+ .on("httpHeaders", function (statusCode, headers) {
136
+ if (!!download)
137
+ res.set("Content-Disposition", contentDisposition(file.filename));
138
+ res.set("Content-Length", headers["content-length"]);
139
+ this.response.httpResponse.createUnbufferedStream().pipe(res);
140
+ })
141
+ .send();
142
+ } else {
143
+ // Use legacy file download
144
+ res.download(file.location, file.filename);
145
+ }
146
+ },
147
+
148
+ unlinkObject: function (file) {
149
+ if (file.s3_store) {
150
+ var s3 = createS3Client();
151
+ return new Promise((resolve, reject) => {
152
+ s3.deleteObject(
153
+ {
154
+ Bucket: getState().getConfig("storage_s3_bucket"),
155
+ Key: file.location,
156
+ },
157
+ (err, data) => {
158
+ if (err) reject(err);
159
+ else resolve(data);
160
+ }
161
+ );
162
+ });
163
+ } else {
164
+ return fs.unlink(file.location);
165
+ }
166
+ },
167
+ };