@saltcorn/server 0.8.5 → 0.8.6-beta.10

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/fields.js CHANGED
@@ -83,6 +83,13 @@ const fieldForm = async (req, fkey_opts, existing_names, id, hasData) => {
83
83
  if (!s || s === "") return req.__("Missing label");
84
84
  if (!id && existing_names.includes(Field.labelToName(s)))
85
85
  return req.__("Column %s already exists", s);
86
+ if (Field.labelToName(s) === "row")
87
+ return req.__("Not a valid field name");
88
+ try {
89
+ new Function(Field.labelToName(s), "return;");
90
+ } catch {
91
+ return req.__("Not a valid field name");
92
+ }
86
93
  },
87
94
  }),
88
95
  // description
@@ -464,14 +471,11 @@ const fieldFlow = (req) =>
464
471
  },
465
472
  {
466
473
  name: req.__("Default"),
467
- onlyWhen: async (context) => {
468
- if (!context.required || context.id || context.calculated)
469
- return false;
474
+ onlyWhen: async (context) => context.required && !context.calculated,
475
+
476
+ form: async (context) => {
470
477
  const table = await Table.findOne({ id: context.table_id });
471
478
  const nrows = await table.countRows();
472
- return nrows > 0;
473
- },
474
- form: async (context) => {
475
479
  const formfield = new Field({
476
480
  name: "default",
477
481
  label: req.__("Default"),
@@ -484,12 +488,28 @@ const fieldFlow = (req) =>
484
488
  },
485
489
  });
486
490
  await formfield.fill_fkey_options();
487
- return new Form({
488
- blurb: req.__(
489
- "A default value is required when adding required fields to nonempty tables"
490
- ),
491
- fields: [formfield],
491
+ const defaultOptional = nrows === 0 || context.id;
492
+ if (defaultOptional) formfield.showIf = { set_default: true };
493
+
494
+ const form = new Form({
495
+ blurb: defaultOptional
496
+ ? req.__("Set a default value for missing data")
497
+ : req.__(
498
+ "A default value is required when adding required fields to nonempty tables"
499
+ ),
500
+ fields: [
501
+ ...(defaultOptional
502
+ ? [{ name: "set_default", label: "Set Default", type: "Bool" }]
503
+ : []),
504
+ formfield,
505
+ ],
492
506
  });
507
+ if (
508
+ typeof context.default !== "undefined" &&
509
+ context.default !== null
510
+ )
511
+ form.values.set_default = true;
512
+ return form;
493
513
  },
494
514
  },
495
515
  ],
@@ -717,7 +737,7 @@ router.post(
717
737
  error_catcher(async (req, res) => {
718
738
  const { tableName, fieldName, fieldview } = req.params;
719
739
  const table = await Table.findOne({ name: tableName });
720
- const role = req.user && req.user.id ? req.user.role_id : 10;
740
+ const role = req.user && req.user.id ? req.user.role_id : 100;
721
741
 
722
742
  const fields = await table.getFields();
723
743
  let row = { ...req.body };
package/routes/files.js CHANGED
@@ -14,7 +14,12 @@ const resizer = require("resize-with-sharp-or-jimp");
14
14
  const db = require("@saltcorn/data/db");
15
15
 
16
16
  const { renderForm } = require("@saltcorn/markup");
17
- const { isAdmin, error_catcher, setTenant } = require("./utils.js");
17
+ const {
18
+ isAdmin,
19
+ error_catcher,
20
+ setTenant,
21
+ is_relative_url,
22
+ } = require("./utils.js");
18
23
  const { h1, div, text } = require("@saltcorn/markup/tags");
19
24
  const { editRoleForm, fileUploadForm } = require("../markup/forms.js");
20
25
  const { strictParseInt } = require("@saltcorn/data/plugin-helper");
@@ -125,7 +130,7 @@ router.get(
125
130
  router.get(
126
131
  "/download/*",
127
132
  error_catcher(async (req, res) => {
128
- const role = req.user && req.user.id ? req.user.role_id : 10;
133
+ const role = req.user && req.user.id ? req.user.role_id : 100;
129
134
  const user_id = req.user && req.user.id;
130
135
  const serve_path = req.params[0];
131
136
  const file = await File.findOne(serve_path);
@@ -150,7 +155,7 @@ router.post(
150
155
  isAdmin,
151
156
 
152
157
  error_catcher(async (req, res) => {
153
- const role = req.user && req.user.id ? req.user.role_id : 10;
158
+ const role = req.user && req.user.id ? req.user.role_id : 100;
154
159
  const user_id = req.user && req.user.id;
155
160
  const files = req.body.files;
156
161
  const location = req.body.location;
@@ -184,7 +189,7 @@ router.post(
184
189
  router.get(
185
190
  "/serve/*",
186
191
  error_catcher(async (req, res) => {
187
- const role = req.user && req.user.id ? req.user.role_id : 10;
192
+ const role = req.user && req.user.id ? req.user.role_id : 100;
188
193
  const user_id = req.user && req.user.id;
189
194
  const serve_path = req.params[0];
190
195
  //let file;
@@ -196,7 +201,7 @@ router.get(
196
201
  (role <= file.min_role_read || (user_id && user_id === file.user_id))
197
202
  ) {
198
203
  res.type(file.mimetype);
199
- const cacheability = file.min_role_read === 10 ? "public" : "private";
204
+ const cacheability = file.min_role_read === 100 ? "public" : "private";
200
205
  res.set("Cache-Control", `${cacheability}, max-age=86400`);
201
206
  if (file.s3_store) s3storage.serveObject(file, res, false);
202
207
  else res.sendFile(file.location);
@@ -223,7 +228,7 @@ router.get(
223
228
  router.get(
224
229
  "/resize/:width_str/:height_str/*",
225
230
  error_catcher(async (req, res) => {
226
- const role = req.user && req.user.id ? req.user.role_id : 10;
231
+ const role = req.user && req.user.id ? req.user.role_id : 100;
227
232
  const user_id = req.user && req.user.id;
228
233
  const { width_str, height_str } = req.params;
229
234
  const serve_path = req.params[0];
@@ -235,7 +240,7 @@ router.get(
235
240
  (role <= file.min_role_read || (user_id && user_id === file.user_id))
236
241
  ) {
237
242
  res.type(file.mimetype);
238
- const cacheability = file.min_role_read === 10 ? "public" : "private";
243
+ const cacheability = file.min_role_read === 100 ? "public" : "private";
239
244
  res.set("Cache-Control", `${cacheability}, max-age=86400`);
240
245
  //TODO s3
241
246
  if (file.s3_store) s3storage.serveObject(file, res, false);
@@ -381,7 +386,7 @@ router.post(
381
386
  let { folder } = req.body;
382
387
  let jsonResp = {};
383
388
  const min_role_upload = getState().getConfig("min_role_upload", 1);
384
- const role = req.user && req.user.id ? req.user.role_id : 10;
389
+ const role = req.user && req.user.id ? req.user.role_id : 100;
385
390
  let file_for_redirect;
386
391
  if (role > +min_role_upload) {
387
392
  if (!req.xhr) req.flash("warning", req.__("Not authorized"));
@@ -441,6 +446,7 @@ router.post(
441
446
  isAdmin,
442
447
  error_catcher(async (req, res) => {
443
448
  const serve_path = req.params[0];
449
+ const { redirect } = req.query;
444
450
  const f = await File.findOne(serve_path);
445
451
  if (!f) {
446
452
  req.flash("error", "File not found");
@@ -460,7 +466,12 @@ router.post(
460
466
  }
461
467
  req.flash("error", result.error);
462
468
  }
463
- res.redirect(`/files?dir=${encodeURIComponent(f.current_folder)}`);
469
+ if (!req.xhr)
470
+ res.redirect(
471
+ (is_relative_url(redirect) && redirect) ||
472
+ `/files?dir=${encodeURIComponent(f.current_folder)}`
473
+ );
474
+ else res.json({ success: true });
464
475
  })
465
476
  );
466
477
 
@@ -426,7 +426,7 @@ const welcome_page = async (req) => {
426
426
  * @returns {Promise<void>}
427
427
  */
428
428
  const no_views_logged_in = async (req, res) => {
429
- const role = req.user && req.user.id ? req.user.role_id : 10;
429
+ const role = req.user && req.user.id ? req.user.role_id : 100;
430
430
  if (role > 1 || req.user.tenant !== db.getTenantSchema())
431
431
  res.sendWrap(req.__("Hello"), req.__("Welcome to Saltcorn!"));
432
432
  else {
@@ -463,7 +463,7 @@ const no_views_logged_in = async (req, res) => {
463
463
  const get_config_response = async (role_id, res, req) => {
464
464
  const modernCfg = getState().getConfig("home_page_by_role", false);
465
465
  // predefined roles
466
- const legacy_role = { 10: "public", 8: "user", 4: "staff", 1: "admin" }[
466
+ const legacy_role = { 100: "public", 80: "user", 40: "staff", 1: "admin" }[
467
467
  role_id
468
468
  ];
469
469
  let homeCfg = modernCfg && modernCfg[role_id];
@@ -497,7 +497,7 @@ module.exports =
497
497
  */
498
498
  async (req, res) => {
499
499
  const isAuth = req.user && req.user.id;
500
- const role_id = req.user ? req.user.role_id : 10;
500
+ const role_id = req.user ? req.user.role_id : 100;
501
501
  const cfgResp = await get_config_response(role_id, res, req);
502
502
  if (cfgResp) return;
503
503
 
package/routes/index.js CHANGED
@@ -1,44 +1,5 @@
1
1
  /**
2
2
  * Index is Main Router of App
3
- * @category server
4
- * @module routes/index
5
- * @subcategory routes
6
- */
7
-
8
- /**
9
- * All files in the routes module.
10
- * @namespace routes_overview
11
- * @property {module:routes/actions} actions
12
- * @property {module:routes/admin} admin
13
- * @property {module:routes/api} api
14
- * @property {module:routes/config} config
15
- * @property {module:routes/crashlog} crashlog
16
- * @property {module:routes/delete} delete
17
- * @property {module:routes/edit} edit
18
- * @property {module:routes/eventlog} eventlog
19
- * @property {module:routes/events} events
20
- * @property {module:routes/fields} fields
21
- * @property {module:routes/files} files
22
- * @property {module:routes/homepage} homepage
23
- * @property {module:routes/infoarch} infoarch
24
- * @property {module:routes/library} library
25
- * @property {module:routes/list} list
26
- * @property {module:routes/menu} menu
27
- * @property {module:routes/packs} packs
28
- * @property {module:routes/page} page
29
- * @property {module:routes/pageedit} pageedit
30
- * @property {module:routes/plugins} plugins
31
- * @property {module:routes/scapi} scapi
32
- * @property {module:routes/search} search
33
- * @property {module:routes/settings} settings
34
- * @property {module:routes/tables} tables
35
- * @property {module:routes/tenant} tenant
36
- * @property {module:routes/utils} utils
37
- * @property {module:routes/view} view
38
- * @property {module:routes/viewedit} viewedit
39
- *
40
- * @category server
41
- * @subcategory routes
42
3
  */
43
4
 
44
5
  const table = require("./tables");
@@ -71,7 +32,8 @@ const useradmin = require("../auth/admin");
71
32
  const roleadmin = require("../auth/roleadmin");
72
33
  const tags = require("./tags");
73
34
  const tagentries = require("./tag_entries");
74
- const dataDiagram = require("./diagram");
35
+ const diagram = require("./diagram");
36
+ const sync = require("./sync");
75
37
 
76
38
  module.exports =
77
39
  /**
@@ -109,5 +71,6 @@ module.exports =
109
71
  app.use("/roleadmin", roleadmin);
110
72
  app.use("/tag", tags);
111
73
  app.use("/tag-entries", tagentries);
112
- app.use("/diagram", dataDiagram);
74
+ app.use("/diagram", diagram);
75
+ app.use("/sync", sync);
113
76
  };
package/routes/menu.js CHANGED
@@ -454,7 +454,7 @@ router.post(
454
454
  "/runaction/:name",
455
455
  error_catcher(async (req, res) => {
456
456
  const { name } = req.params;
457
- const role = (req.user || {}).role_id || 10;
457
+ const role = (req.user || {}).role_id || 100;
458
458
  const state = getState();
459
459
  const menu_items = state.getConfig("menu_items");
460
460
  let menu_item;
package/routes/page.js CHANGED
@@ -42,7 +42,7 @@ router.get(
42
42
  state.log(3, `Route /page/${pagename} user=${req.user?.id}`);
43
43
  const tic = state.logLevel >= 5 ? new Date() : null;
44
44
 
45
- const role = req.user && req.user.id ? req.user.role_id : 10;
45
+ const role = req.user && req.user.id ? req.user.role_id : 100;
46
46
  const db_page = await Page.findOne({ name: pagename });
47
47
  if (db_page && role <= db_page.min_role) {
48
48
  const contents = await db_page.run(req.query, { res, req });
@@ -104,7 +104,7 @@ router.post(
104
104
  "/:pagename/action/:rndid",
105
105
  error_catcher(async (req, res) => {
106
106
  const { pagename, rndid } = req.params;
107
- const role = req.user && req.user.id ? req.user.role_id : 10;
107
+ const role = req.user && req.user.id ? req.user.role_id : 100;
108
108
  const db_page = await Page.findOne({ name: pagename });
109
109
  if (db_page && role <= db_page.min_role) {
110
110
  let col;
@@ -387,7 +387,7 @@ router.get(
387
387
  isAdmin,
388
388
  error_catcher(async (req, res) => {
389
389
  const { pagename } = req.params;
390
- const page = await Page.findOne({ name: pagename });
390
+ const [page] = await Page.find({ name: pagename });
391
391
  if (!page) {
392
392
  req.flash("error", req.__(`Page %s not found`, pagename));
393
393
  res.redirect(`/pageedit`);
@@ -506,7 +506,7 @@ router.post(
506
506
  const valres = form.validate(req.body);
507
507
  if (valres.success) {
508
508
  const home_page_by_role =
509
- getState().getConfigCopy("home_page_by_role", []) || [];
509
+ getState().getConfigCopy("home_page_by_role", {}) || {};
510
510
  for (const role of roles) {
511
511
  home_page_by_role[role.id] = valres.success[role.role];
512
512
  }
package/routes/scapi.js CHANGED
@@ -49,7 +49,7 @@ function accessAllowedRead(req, user) {
49
49
  ? req.user.role_id
50
50
  : user && user.role_id
51
51
  ? user.role_id
52
- : 10;
52
+ : 100;
53
53
 
54
54
  if (role === 1) return true;
55
55
  return false;
package/routes/search.js CHANGED
@@ -165,7 +165,7 @@ const searchForm = () =>
165
165
  * @returns {Promise<void>}
166
166
  */
167
167
  const runSearch = async ({ q, _page, table }, req, res) => {
168
- const role = (req.user || {}).role_id || 10;
168
+ const role = (req.user || {}).role_id || 100;
169
169
  // globalSearch contains list of pairs: table, view
170
170
  const cfg = getState().getConfig("globalSearch");
171
171
  const page_size = getState().getConfig("search_page_size");
@@ -269,10 +269,9 @@ const runSearch = async ({ q, _page, table }, req, res) => {
269
269
  router.get(
270
270
  "/",
271
271
  error_catcher(async (req, res) => {
272
-
273
272
  const min_role = getState().getConfig("min_role_search");
274
- const role = (req.user || {}).role_id || 10;
275
- if(role>min_role){
273
+ const role = (req.user || {}).role_id || 100;
274
+ if (role > min_role) {
276
275
  res.redirect("/"); // silent redirect to home page
277
276
  return;
278
277
  }
@@ -283,7 +282,7 @@ router.get(
283
282
  const cfg = getState().getConfig("globalSearch");
284
283
 
285
284
  if (!cfg) {
286
- const role = (req.user || {}).role_id || 10;
285
+ const role = (req.user || {}).role_id || 100;
287
286
 
288
287
  req.flash("warning", req.__("Search not configured"));
289
288
  res.redirect(role === 1 ? "/search/config" : "/");
package/routes/sync.js ADDED
@@ -0,0 +1,84 @@
1
+ const { error_catcher } = require("./utils.js");
2
+ const Router = require("express-promise-router");
3
+ const db = require("@saltcorn/data/db");
4
+ const { getState } = require("@saltcorn/data/db/state");
5
+ const Table = require("@saltcorn/data/models/table");
6
+
7
+ const router = new Router();
8
+ module.exports = router;
9
+
10
+ const pickFields = (table, row) => {
11
+ const result = {};
12
+ for (const { name, type } of table.getFields()) {
13
+ if (name === "id") continue;
14
+ if (type?.name === "Date") {
15
+ result[name] = row[name] ? new Date(row[name]) : undefined;
16
+ } else {
17
+ result[name] = row[name];
18
+ }
19
+ }
20
+ return result;
21
+ };
22
+
23
+ const allowInsert = (table, user) => {
24
+ const role = user?.role_id || 100;
25
+ return table.min_role_write >= role;
26
+ };
27
+
28
+ const throwWithCode = (message, code) => {
29
+ const err = new Error(message);
30
+ err.statusCode = code;
31
+ throw err;
32
+ };
33
+
34
+ /**
35
+ * insert the offline data uploaded by the mobile-app
36
+ */
37
+ router.post(
38
+ "/table_data",
39
+ error_catcher(async (req, res) => {
40
+ // TODO sqlite
41
+ getState().log(
42
+ 4,
43
+ `POST /sync/table_data user: '${req.user ? req.user.id : "public"}'`
44
+ );
45
+ let aborted = false;
46
+ req.socket.on("close", () => {
47
+ aborted = true;
48
+ });
49
+ req.socket.on("timeout", () => {
50
+ aborted = true;
51
+ });
52
+ const client = db.isSQLite ? db : await db.getClient();
53
+ try {
54
+ await client.query("BEGIN");
55
+ await client.query("SET CONSTRAINTS ALL DEFERRED");
56
+ for (const [tblName, offlineRows] of Object.entries(req.body.data) ||
57
+ []) {
58
+ const table = Table.findOne({ name: tblName });
59
+ if (!table) throw new Error(`The table '${tblName}' does not exist.`);
60
+ if (!allowInsert(table, req.user))
61
+ throwWithCode(req.__("Not authorized"), 401);
62
+ if (tblName !== "users") {
63
+ for (const newRow of offlineRows.map((row) =>
64
+ pickFields(table, row)
65
+ )) {
66
+ if (aborted) throw new Error("connection closed by client");
67
+ await db.insert(table.name, newRow, { client: client });
68
+ }
69
+ }
70
+ }
71
+ if (aborted) throw new Error("connection closed by client");
72
+ await client.query("COMMIT");
73
+ res.json({ success: true });
74
+ } catch (error) {
75
+ await client.query("ROLLBACK");
76
+ getState().log(2, `POST /sync/table_data error: '${error.message}'`);
77
+ res
78
+ .status(error.statusCode || 400)
79
+ .json({ error: error.message || error });
80
+ } finally {
81
+ if (!db.isSQLite) await client.release(true);
82
+ }
83
+ })
84
+ );
package/routes/tables.js CHANGED
@@ -17,6 +17,7 @@ const {
17
17
  link,
18
18
  settingsDropdown,
19
19
  post_delete_btn,
20
+ post_btn,
20
21
  post_dropdown_item,
21
22
  } = require("@saltcorn/markup");
22
23
  const {
@@ -52,7 +53,9 @@ const { getState } = require("@saltcorn/data/db/state");
52
53
  const { cardHeaderTabs } = require("@saltcorn/markup/layout_utils");
53
54
  const { tablesList } = require("./common_lists");
54
55
  const { InvalidConfiguration } = require("@saltcorn/data/utils");
56
+ const { sleep } = require("@saltcorn/data/utils");
55
57
 
58
+ const path = require("path");
56
59
  /**
57
60
  * @type {object}
58
61
  * @const
@@ -538,11 +541,7 @@ const attribBadges = (f) => {
538
541
  let s = "";
539
542
  if (f.attributes) {
540
543
  Object.entries(f.attributes).forEach(([k, v]) => {
541
- if (
542
- ["summary_field", "default", "on_delete_cascade", "on_delete"].includes(
543
- k
544
- )
545
- )
544
+ if (["summary_field", "on_delete_cascade", "on_delete"].includes(k))
546
545
  return;
547
546
  if (v || v === 0) s += badge("secondary", k);
548
547
  });
@@ -912,10 +911,12 @@ router.post(
912
911
  rest.provider_name !== "Database table"
913
912
  ) {
914
913
  const table = await Table.create(name, rest);
914
+ await sleep(500); // Allow other workers to load this view
915
915
  res.redirect(`/table/provider-cfg/${table.id}`);
916
916
  } else {
917
917
  delete rest.provider_name;
918
918
  const table = await Table.create(name, rest);
919
+ await sleep(500); // Allow other workers to load this view
919
920
  req.flash("success", req.__(`Table %s created`, name));
920
921
  res.redirect(`/table/${table.id}`);
921
922
  }
@@ -1463,6 +1464,89 @@ router.post(
1463
1464
  })
1464
1465
  );
1465
1466
 
1467
+ const previewCSV = async ({ newPath, table, req, res, full }) => {
1468
+ let parse_res;
1469
+ try {
1470
+ parse_res = await table.import_csv_file(newPath, {
1471
+ recalc_stored: true,
1472
+ no_table_write: true,
1473
+ });
1474
+ } catch (e) {
1475
+ parse_res = { error: e.message };
1476
+ }
1477
+ if (parse_res.error) {
1478
+ if (parse_res.error) req.flash("error", parse_res.error);
1479
+ await fs.unlink(newPath);
1480
+ res.redirect(`/table/${table.id}`);
1481
+ } else {
1482
+ const rows = parse_res.rows || [];
1483
+ res.sendWrap(req.__(`Import table %s`, table.name), {
1484
+ above: [
1485
+ {
1486
+ type: "breadcrumbs",
1487
+ crumbs: [
1488
+ { text: req.__("Tables"), href: "/table" },
1489
+ { href: `/table/${table.id}`, text: table.name },
1490
+ {
1491
+ text: req.__("Import CSV"),
1492
+ },
1493
+ ],
1494
+ },
1495
+ {
1496
+ type: "card",
1497
+ title: req.__(`Import CSV`),
1498
+ contents: div(
1499
+ {
1500
+ "data-csv-filename": path.basename(newPath),
1501
+ },
1502
+ p(parse_res.success),
1503
+ post_btn(
1504
+ `/files/delete/${path.basename(newPath)}?redirect=/table/${
1505
+ table.id
1506
+ }}`,
1507
+ "Cancel",
1508
+ req.csrfToken(),
1509
+ {
1510
+ btnClass: "btn-danger",
1511
+ formClass: "d-inline me-2",
1512
+ icon: "fa fa-times",
1513
+ }
1514
+ ),
1515
+ post_btn(
1516
+ `/table/finish_upload_to_table/${table.name}/${path.basename(
1517
+ newPath
1518
+ )}`,
1519
+ "Proceed",
1520
+ req.csrfToken(),
1521
+ { icon: "fa fa-check", formClass: "d-inline" }
1522
+ )
1523
+ ),
1524
+ },
1525
+ {
1526
+ type: "card",
1527
+ title: req.__(`Preview`),
1528
+ contents: div(
1529
+ mkTable(
1530
+ table.fields.map((f) => ({ label: f.name, key: f.name })),
1531
+ full ? rows : rows.slice(0, 10)
1532
+ ),
1533
+ !full &&
1534
+ rows.length > 10 &&
1535
+ a(
1536
+ {
1537
+ href: `/table/preview_full_csv_file/${
1538
+ table.name
1539
+ }/${path.basename(newPath)}`,
1540
+ },
1541
+ `See all ${rows.length} rows`
1542
+ )
1543
+ ),
1544
+ },
1545
+ ],
1546
+ });
1547
+ }
1548
+ };
1549
+
1466
1550
  /**
1467
1551
  * Import Table Data from CSV POST handler
1468
1552
  * @name post/upload_to_table/:name,
@@ -1486,15 +1570,39 @@ router.post(
1486
1570
  const newPath = File.get_new_path();
1487
1571
  await req.files.file.mv(newPath);
1488
1572
  //console.log(req.files.file.data)
1573
+ await previewCSV({ newPath, table, res, req });
1574
+ })
1575
+ );
1576
+
1577
+ router.get(
1578
+ "/preview_full_csv_file/:name/:filename",
1579
+ isAdmin,
1580
+ error_catcher(async (req, res) => {
1581
+ const { name, filename } = req.params;
1582
+ const table = await Table.findOne({ name });
1583
+ const f = await File.findOne(filename);
1584
+ await previewCSV({ newPath: f.location, table, res, req, full: true });
1585
+ })
1586
+ );
1587
+
1588
+ router.post(
1589
+ "/finish_upload_to_table/:name/:filename",
1590
+ isAdmin,
1591
+ error_catcher(async (req, res) => {
1592
+ const { name, filename } = req.params;
1593
+ const table = await Table.findOne({ name });
1594
+ const f = await File.findOne(filename);
1595
+
1489
1596
  try {
1490
- const parse_res = await table.import_csv_file(newPath, true);
1597
+ const parse_res = await table.import_csv_file(f.location, {
1598
+ recalc_stored: true,
1599
+ });
1491
1600
  if (parse_res.error) req.flash("error", parse_res.error);
1492
1601
  else req.flash("success", parse_res.success);
1493
1602
  } catch (e) {
1494
1603
  req.flash("error", e.message);
1495
1604
  }
1496
-
1497
- await fs.unlink(newPath);
1605
+ await fs.unlink(f.location);
1498
1606
  res.redirect(`/table/${table.id}`);
1499
1607
  })
1500
1608
  );