@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/app.js +3 -3
- package/auth/admin.js +4 -0
- package/auth/roleadmin.js +7 -4
- package/auth/routes.js +1 -1
- package/errors.js +1 -1
- package/locales/en.json +19 -2
- package/locales/pl.json +32 -1
- package/locales/ru.json +38 -2
- package/locales/uk.json +1170 -0
- package/package.json +8 -8
- package/public/dayjs.min.js +1 -0
- package/public/saltcorn-common.js +97 -69
- package/public/saltcorn.css +17 -0
- package/public/saltcorn.js +3 -3
- package/routes/admin.js +26 -0
- package/routes/api.js +27 -48
- package/routes/common_lists.js +4 -1
- package/routes/delete.js +3 -3
- package/routes/edit.js +1 -1
- package/routes/fields.js +32 -12
- package/routes/files.js +20 -9
- package/routes/homepage.js +3 -3
- package/routes/index.js +4 -41
- package/routes/menu.js +1 -1
- package/routes/page.js +2 -2
- package/routes/pageedit.js +2 -2
- package/routes/scapi.js +1 -1
- package/routes/search.js +4 -5
- package/routes/sync.js +84 -0
- package/routes/tables.js +116 -8
- package/routes/tenant.js +74 -27
- package/routes/view.js +3 -3
- package/routes/viewedit.js +56 -13
- package/tests/admin.test.js +8 -8
- package/tests/auth.test.js +13 -13
- package/tests/crud.test.js +2 -2
- package/tests/files.test.js +6 -6
- package/tests/kittens.test.js +11 -11
- package/tests/page.test.js +4 -5
- package/tests/sync.test.js +108 -0
- package/tests/table.test.js +19 -7
- package/tests/tenant.test.js +3 -3
- package/tests/view.test.js +128 -1
- package/tests/viewedit.test.js +4 -4
- package/wrapper.js +3 -2
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
|
-
|
|
469
|
-
|
|
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
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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 :
|
|
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 {
|
|
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 :
|
|
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 :
|
|
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 :
|
|
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 ===
|
|
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 :
|
|
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 ===
|
|
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 :
|
|
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
|
-
|
|
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
|
|
package/routes/homepage.js
CHANGED
|
@@ -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 :
|
|
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 = {
|
|
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 :
|
|
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
|
|
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",
|
|
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 ||
|
|
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 :
|
|
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 :
|
|
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;
|
package/routes/pageedit.js
CHANGED
|
@@ -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.
|
|
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
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 ||
|
|
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 ||
|
|
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 ||
|
|
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(
|
|
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
|
);
|