@saltcorn/server 1.0.0-beta.9 → 1.0.0-rc.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 +4 -7
- package/auth/admin.js +1 -0
- package/auth/routes.js +4 -1
- package/help/Aggregation where formula.tmd +11 -0
- package/help/Calculated fields.tmd +28 -0
- package/help/Date format.tmd +55 -0
- package/help/Protected fields.tmd +4 -0
- package/help/Snapshots.tmd +19 -0
- package/help/Table history.tmd +42 -0
- package/help/index.js +4 -1
- package/load_plugins.js +90 -8
- package/locales/en.json +14 -1
- package/locales/pl.json +407 -85
- package/package.json +11 -11
- package/public/gridedit.js +3 -1
- package/public/saltcorn-builder.css +0 -7
- package/public/saltcorn-common.js +196 -101
- package/public/saltcorn.css +17 -0
- package/public/saltcorn.js +91 -17
- package/routes/actions.js +19 -25
- package/routes/admin.js +256 -54
- package/routes/api.js +36 -1
- package/routes/delete.js +4 -1
- package/routes/eventlog.js +2 -1
- package/routes/fields.js +21 -5
- package/routes/files.js +9 -2
- package/routes/infoarch.js +40 -2
- package/routes/list.js +1 -1
- package/routes/menu.js +3 -0
- package/routes/pageedit.js +9 -2
- package/routes/plugins.js +124 -28
- package/routes/scapi.js +19 -0
- package/routes/search.js +6 -1
- package/routes/sync.js +3 -5
- package/routes/tables.js +14 -4
- package/routes/viewedit.js +7 -4
- package/serve.js +17 -3
- package/tests/api.test.js +29 -0
- package/tests/fields.test.js +32 -0
- package/tests/plugin_install.test.js +235 -0
- package/tests/plugins.test.js +140 -0
package/routes/infoarch.js
CHANGED
|
@@ -14,7 +14,7 @@ const {
|
|
|
14
14
|
} = require("../markup/admin.js");
|
|
15
15
|
const { getState } = require("@saltcorn/data/db/state");
|
|
16
16
|
const { div, a, i, text } = require("@saltcorn/markup/tags");
|
|
17
|
-
const { mkTable, renderForm } = require("@saltcorn/markup");
|
|
17
|
+
const { mkTable, renderForm, post_delete_btn } = require("@saltcorn/markup");
|
|
18
18
|
const Form = require("@saltcorn/data/models/form");
|
|
19
19
|
|
|
20
20
|
/**
|
|
@@ -119,6 +119,15 @@ router.get(
|
|
|
119
119
|
})
|
|
120
120
|
: "",
|
|
121
121
|
},
|
|
122
|
+
{
|
|
123
|
+
label: req.__("Delete"),
|
|
124
|
+
key: (r) =>
|
|
125
|
+
post_delete_btn(
|
|
126
|
+
`/site-structure/localizer/delete-lang/${r.locale}`,
|
|
127
|
+
req,
|
|
128
|
+
r.name
|
|
129
|
+
),
|
|
130
|
+
},
|
|
122
131
|
],
|
|
123
132
|
Object.values(cfgLangs)
|
|
124
133
|
),
|
|
@@ -234,7 +243,14 @@ router.post(
|
|
|
234
243
|
isAdmin,
|
|
235
244
|
error_catcher(async (req, res) => {
|
|
236
245
|
const { lang, defstring } = req.params;
|
|
237
|
-
|
|
246
|
+
if (
|
|
247
|
+
lang === "__proto__" ||
|
|
248
|
+
defstring === "__proto__" ||
|
|
249
|
+
lang === "constructor"
|
|
250
|
+
) {
|
|
251
|
+
res.redirect(`/`);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
238
254
|
const cfgStrings = getState().getConfigCopy("localizer_strings");
|
|
239
255
|
if (cfgStrings[lang]) cfgStrings[lang][defstring] = text(req.body.value);
|
|
240
256
|
else cfgStrings[lang] = { [defstring]: text(req.body.value) };
|
|
@@ -280,3 +296,25 @@ router.post(
|
|
|
280
296
|
}
|
|
281
297
|
})
|
|
282
298
|
);
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @name post/localizer/save-lang
|
|
302
|
+
* @function
|
|
303
|
+
* @memberof module:routes/infoarch~infoarchRouter
|
|
304
|
+
* @function
|
|
305
|
+
*/
|
|
306
|
+
router.post(
|
|
307
|
+
"/localizer/delete-lang/:lang",
|
|
308
|
+
isAdmin,
|
|
309
|
+
error_catcher(async (req, res) => {
|
|
310
|
+
const { lang } = req.params;
|
|
311
|
+
|
|
312
|
+
const cfgLangs = getState().getConfig("localizer_languages");
|
|
313
|
+
if (cfgLangs[lang]) {
|
|
314
|
+
delete cfgLangs[lang];
|
|
315
|
+
await getState().setConfig("localizer_languages", cfgLangs);
|
|
316
|
+
}
|
|
317
|
+
if (!req.xhr) res.redirect(`/site-structure/localizer`);
|
|
318
|
+
else res.json({ success: "ok" });
|
|
319
|
+
})
|
|
320
|
+
);
|
package/routes/list.js
CHANGED
|
@@ -411,7 +411,7 @@ router.get(
|
|
|
411
411
|
ajax_indicator(false);
|
|
412
412
|
//if (item._versions) item._versions = +item._versions + 1;
|
|
413
413
|
//data.resolve(fixKeys(item));
|
|
414
|
-
if(resp.success &&typeof resp.success ==="number" && !row.id) {
|
|
414
|
+
if(resp.success &&(typeof resp.success ==="number" || typeof resp.success ==="string") && !row.id) {
|
|
415
415
|
window.tabulator_table.updateRow(cell.getRow(), {id: resp.success});
|
|
416
416
|
}
|
|
417
417
|
|
package/routes/menu.js
CHANGED
package/routes/pageedit.js
CHANGED
|
@@ -183,6 +183,12 @@ const pageBuilderData = async (req, context) => {
|
|
|
183
183
|
});
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
|
+
const actionsNotRequiringRow = Trigger.action_options({
|
|
187
|
+
notRequireRow: true,
|
|
188
|
+
apiNeverTriggers: true,
|
|
189
|
+
builtInLabel: "Page Actions",
|
|
190
|
+
builtIns: ["GoBack"],
|
|
191
|
+
});
|
|
186
192
|
const library = (await Library.find({})).filter((l) => l.suitableFor("page"));
|
|
187
193
|
const fixed_state_fields = {};
|
|
188
194
|
for (const view of views) {
|
|
@@ -228,7 +234,7 @@ const pageBuilderData = async (req, context) => {
|
|
|
228
234
|
images,
|
|
229
235
|
pages,
|
|
230
236
|
page_groups,
|
|
231
|
-
actions,
|
|
237
|
+
actions: actionsNotRequiringRow,
|
|
232
238
|
builtInActions: ["GoBack"],
|
|
233
239
|
library,
|
|
234
240
|
min_role: context.min_role,
|
|
@@ -642,7 +648,8 @@ router.post(
|
|
|
642
648
|
const { pagename } = req.params;
|
|
643
649
|
|
|
644
650
|
let redirectTarget =
|
|
645
|
-
req.query.on_done_redirect &&
|
|
651
|
+
req.query.on_done_redirect &&
|
|
652
|
+
is_relative_url("/" + req.query.on_done_redirect)
|
|
646
653
|
? `/${req.query.on_done_redirect}`
|
|
647
654
|
: "/pageedit";
|
|
648
655
|
const page = await Page.findOne({ name: pagename });
|
package/routes/plugins.js
CHANGED
|
@@ -49,6 +49,8 @@ const {
|
|
|
49
49
|
input,
|
|
50
50
|
label,
|
|
51
51
|
text,
|
|
52
|
+
script,
|
|
53
|
+
domReady,
|
|
52
54
|
} = require("@saltcorn/markup/tags");
|
|
53
55
|
const { search_bar } = require("@saltcorn/markup/helpers");
|
|
54
56
|
const fs = require("fs");
|
|
@@ -59,6 +61,10 @@ const { sleep, removeNonWordChars } = require("@saltcorn/data/utils");
|
|
|
59
61
|
const { loadAllPlugins } = require("../load_plugins");
|
|
60
62
|
const npmFetch = require("npm-registry-fetch");
|
|
61
63
|
const PluginInstaller = require("@saltcorn/plugins-loader/plugin_installer");
|
|
64
|
+
const {
|
|
65
|
+
supportedVersion,
|
|
66
|
+
isVersionSupported,
|
|
67
|
+
} = require("@saltcorn/plugins-loader/stable_versioning");
|
|
62
68
|
|
|
63
69
|
/**
|
|
64
70
|
* @type {object}
|
|
@@ -177,6 +183,8 @@ const get_store_items = async () => {
|
|
|
177
183
|
has_auth: plugin.has_auth,
|
|
178
184
|
unsafe: plugin.unsafe,
|
|
179
185
|
source: plugin.source,
|
|
186
|
+
ready_for_mobile:
|
|
187
|
+
plugin.ready_for_mobile && plugin.ready_for_mobile(plugin.name),
|
|
180
188
|
}))
|
|
181
189
|
.filter((p) => !p.unsafe || isRoot || tenants_unsafe_plugins);
|
|
182
190
|
const local_logins = installed_plugins
|
|
@@ -190,6 +198,8 @@ const get_store_items = async () => {
|
|
|
190
198
|
github: plugin.source === "github",
|
|
191
199
|
git: plugin.source === "git",
|
|
192
200
|
local: plugin.source === "local",
|
|
201
|
+
ready_for_mobile:
|
|
202
|
+
plugin.ready_for_mobile && plugin.ready_for_mobile(plugin.name),
|
|
193
203
|
}));
|
|
194
204
|
|
|
195
205
|
const pack_items = packs_available.map((pack) => ({
|
|
@@ -274,7 +284,8 @@ const store_item_html = (req) => (item) => ({
|
|
|
274
284
|
item.github && badge("GitHub"),
|
|
275
285
|
item.git && badge("Git"),
|
|
276
286
|
item.local && badge(req.__("Local")),
|
|
277
|
-
item.installed && badge(req.__("Installed"))
|
|
287
|
+
item.installed && badge(req.__("Installed")),
|
|
288
|
+
item.ready_for_mobile && badge(req.__("Mobile"))
|
|
278
289
|
),
|
|
279
290
|
div(item.description || ""),
|
|
280
291
|
item.documentation_link
|
|
@@ -584,7 +595,9 @@ router.get(
|
|
|
584
595
|
error_catcher(async (req, res) => {
|
|
585
596
|
const { name } = req.params;
|
|
586
597
|
const withoutOrg = name.replace(/^@saltcorn\//, "");
|
|
587
|
-
|
|
598
|
+
let plugin = await Plugin.store_by_name(decodeURIComponent(withoutOrg));
|
|
599
|
+
if (!plugin)
|
|
600
|
+
plugin = await Plugin.findOne({ name: decodeURIComponent(name) });
|
|
588
601
|
if (!plugin) {
|
|
589
602
|
getState().log(
|
|
590
603
|
2,
|
|
@@ -603,12 +616,14 @@ router.get(
|
|
|
603
616
|
res.set("Page-Title", req.__("%s versions", text(withoutOrg)));
|
|
604
617
|
const versions = Object.keys(pkgInfo.versions);
|
|
605
618
|
if (versions.length === 0) throw new Error(req.__("No versions found"));
|
|
619
|
+
const tags = pkgInfo["dist-tags"] || {};
|
|
606
620
|
let selected = null;
|
|
607
621
|
if (getState().plugins[plugin.name]) {
|
|
608
622
|
const mod = await load_plugins.requirePlugin(plugin);
|
|
609
623
|
if (mod) selected = mod.version;
|
|
610
624
|
}
|
|
611
625
|
if (!selected) selected = versions[versions.length - 1];
|
|
626
|
+
const scVersion = getState().scVersion;
|
|
612
627
|
return res.send(
|
|
613
628
|
form(
|
|
614
629
|
{
|
|
@@ -618,6 +633,7 @@ router.get(
|
|
|
618
633
|
input({ type: "hidden", name: "_csrf", value: req.csrfToken() }),
|
|
619
634
|
div(
|
|
620
635
|
{ class: "form-group" },
|
|
636
|
+
// version
|
|
621
637
|
label(
|
|
622
638
|
{
|
|
623
639
|
for: "version_select",
|
|
@@ -631,14 +647,49 @@ router.get(
|
|
|
631
647
|
class: "form-control form-select",
|
|
632
648
|
name: "version",
|
|
633
649
|
},
|
|
634
|
-
versions
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
650
|
+
versions
|
|
651
|
+
.filter((v) =>
|
|
652
|
+
isVersionSupported(v, pkgInfo.versions, scVersion)
|
|
653
|
+
)
|
|
654
|
+
.map((version) =>
|
|
655
|
+
option({
|
|
656
|
+
id: `${version}_opt`,
|
|
657
|
+
value: version,
|
|
658
|
+
label: version,
|
|
659
|
+
selected: version === selected,
|
|
660
|
+
})
|
|
661
|
+
)
|
|
662
|
+
),
|
|
663
|
+
// tag
|
|
664
|
+
label(
|
|
665
|
+
{
|
|
666
|
+
for: "tag_select",
|
|
667
|
+
class: "form-label fw-bold mt-2",
|
|
668
|
+
},
|
|
669
|
+
req.__("Tags")
|
|
670
|
+
),
|
|
671
|
+
select(
|
|
672
|
+
{
|
|
673
|
+
id: "tag_select",
|
|
674
|
+
class: "form-control form-select",
|
|
675
|
+
},
|
|
676
|
+
option({
|
|
677
|
+
id: "empty_opt",
|
|
678
|
+
value: "",
|
|
679
|
+
label: req.__("Select tag"),
|
|
680
|
+
selected: true,
|
|
681
|
+
}),
|
|
682
|
+
Object.keys(tags)
|
|
683
|
+
.filter((tag) =>
|
|
684
|
+
isVersionSupported(tags[tag], pkgInfo.versions, scVersion)
|
|
685
|
+
)
|
|
686
|
+
.map((tag) =>
|
|
687
|
+
option({
|
|
688
|
+
id: `${tag}_opt`,
|
|
689
|
+
value: tags[tag],
|
|
690
|
+
label: `${tag} (${tags[tag]})`,
|
|
691
|
+
})
|
|
692
|
+
)
|
|
642
693
|
)
|
|
643
694
|
),
|
|
644
695
|
div(
|
|
@@ -660,7 +711,19 @@ router.get(
|
|
|
660
711
|
req.__("Install")
|
|
661
712
|
)
|
|
662
713
|
)
|
|
663
|
-
)
|
|
714
|
+
) +
|
|
715
|
+
script(
|
|
716
|
+
domReady(`
|
|
717
|
+
document.getElementById('tag_select').onchange = () => {
|
|
718
|
+
const version = document.getElementById('tag_select').value;
|
|
719
|
+
if (version) document.getElementById('version_select').value = version;
|
|
720
|
+
};
|
|
721
|
+
document.getElementById('version_select').onchange = () => {
|
|
722
|
+
const tagSelect = document.getElementById('tag_select');
|
|
723
|
+
tagSelect.value = '';
|
|
724
|
+
};
|
|
725
|
+
`)
|
|
726
|
+
)
|
|
664
727
|
);
|
|
665
728
|
} catch (error) {
|
|
666
729
|
getState().log(
|
|
@@ -836,12 +899,13 @@ router.post(
|
|
|
836
899
|
};
|
|
837
900
|
await plugin.upsert();
|
|
838
901
|
await load_plugins.loadPlugin(plugin);
|
|
902
|
+
|
|
903
|
+
getState().processSend({
|
|
904
|
+
refresh_plugin_cfg: plugin.name,
|
|
905
|
+
tenant: db.getTenantSchema(),
|
|
906
|
+
});
|
|
907
|
+
res.json({ success: "ok" });
|
|
839
908
|
}
|
|
840
|
-
getState().processSend({
|
|
841
|
-
refresh_plugin_cfg: plugin.name,
|
|
842
|
-
tenant: db.getTenantSchema(),
|
|
843
|
-
});
|
|
844
|
-
res.json({ success: "ok" });
|
|
845
909
|
}
|
|
846
910
|
})
|
|
847
911
|
);
|
|
@@ -1168,9 +1232,22 @@ router.get(
|
|
|
1168
1232
|
const update_permitted =
|
|
1169
1233
|
db.getTenantSchema() === db.connectObj.default_schema &&
|
|
1170
1234
|
plugin_db.source === "npm";
|
|
1171
|
-
|
|
1235
|
+
|
|
1236
|
+
let latest =
|
|
1172
1237
|
update_permitted &&
|
|
1173
1238
|
(await get_latest_npm_version(plugin_db.location, 1000));
|
|
1239
|
+
let engineInfos = await load_plugins.getEngineInfos(plugin_db); // with cache
|
|
1240
|
+
let forceFetch = true;
|
|
1241
|
+
if (latest && !engineInfos[latest]) {
|
|
1242
|
+
engineInfos = await load_plugins.getEngineInfos(plugin_db, forceFetch);
|
|
1243
|
+
forceFetch = false;
|
|
1244
|
+
}
|
|
1245
|
+
if (latest && !isVersionSupported(latest, engineInfos)) {
|
|
1246
|
+
latest = supportedVersion(
|
|
1247
|
+
latest,
|
|
1248
|
+
await load_plugins.getEngineInfos(plugin_db, forceFetch)
|
|
1249
|
+
);
|
|
1250
|
+
}
|
|
1174
1251
|
const can_update = update_permitted && latest && mod.version !== latest;
|
|
1175
1252
|
const can_select_version = update_permitted && plugin_db.source === "npm";
|
|
1176
1253
|
let pkgjson;
|
|
@@ -1316,8 +1393,8 @@ router.get(
|
|
|
1316
1393
|
error_catcher(async (req, res) => {
|
|
1317
1394
|
const schema = db.getTenantSchema();
|
|
1318
1395
|
if (schema === db.connectObj.default_schema) {
|
|
1319
|
-
await upgrade_all_tenants_plugins((p, f) =>
|
|
1320
|
-
load_plugins.loadPlugin(p, f)
|
|
1396
|
+
await upgrade_all_tenants_plugins((p, f, forceFetch) =>
|
|
1397
|
+
load_plugins.loadPlugin(p, f, forceFetch)
|
|
1321
1398
|
);
|
|
1322
1399
|
req.flash(
|
|
1323
1400
|
"success",
|
|
@@ -1328,7 +1405,9 @@ router.get(
|
|
|
1328
1405
|
} else {
|
|
1329
1406
|
const installed_plugins = await Plugin.find({});
|
|
1330
1407
|
for (const plugin of installed_plugins) {
|
|
1331
|
-
await plugin.upgrade_version((p, f) =>
|
|
1408
|
+
await plugin.upgrade_version((p, f, forceFetch) =>
|
|
1409
|
+
load_plugins.loadPlugin(p, f, forceFetch)
|
|
1410
|
+
);
|
|
1332
1411
|
}
|
|
1333
1412
|
req.flash("success", req.__(`Modules up-to-date`));
|
|
1334
1413
|
await restart_tenant(loadAllPlugins);
|
|
@@ -1354,7 +1433,12 @@ router.get(
|
|
|
1354
1433
|
const { name } = req.params;
|
|
1355
1434
|
|
|
1356
1435
|
const plugin = await Plugin.findOne({ name });
|
|
1357
|
-
|
|
1436
|
+
const versions = await load_plugins.getEngineInfos(plugin, true);
|
|
1437
|
+
|
|
1438
|
+
await plugin.upgrade_version(
|
|
1439
|
+
(p, f) => load_plugins.loadPlugin(p, f),
|
|
1440
|
+
supportedVersion("latest", versions, require("../package.json").version)
|
|
1441
|
+
);
|
|
1358
1442
|
req.flash("success", req.__(`Module up-to-date`));
|
|
1359
1443
|
|
|
1360
1444
|
res.redirect(`/plugins/info/${encodeURIComponent(plugin.name)}`);
|
|
@@ -1385,11 +1469,12 @@ router.post(
|
|
|
1385
1469
|
res.redirect(`/plugins`);
|
|
1386
1470
|
} else {
|
|
1387
1471
|
try {
|
|
1388
|
-
await load_plugins.loadAndSaveNewPlugin(
|
|
1472
|
+
const msgs = await load_plugins.loadAndSaveNewPlugin(
|
|
1389
1473
|
plugin,
|
|
1390
1474
|
schema === db.connectObj.default_schema || plugin.source === "github"
|
|
1391
1475
|
);
|
|
1392
1476
|
req.flash("success", req.__(`Module %s installed`, plugin.name));
|
|
1477
|
+
for (const msg of msgs) req.flash("warning", msg);
|
|
1393
1478
|
res.redirect(`/plugins`);
|
|
1394
1479
|
} catch (e) {
|
|
1395
1480
|
req.flash("error", `${e.message}`);
|
|
@@ -1488,12 +1573,23 @@ router.post(
|
|
|
1488
1573
|
res.redirect(`/plugins`);
|
|
1489
1574
|
return;
|
|
1490
1575
|
}
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1576
|
+
|
|
1577
|
+
let msgs = null;
|
|
1578
|
+
try {
|
|
1579
|
+
msgs = await load_plugins.loadAndSaveNewPlugin(
|
|
1580
|
+
plugin,
|
|
1581
|
+
forceReInstall,
|
|
1582
|
+
undefined,
|
|
1583
|
+
req.__
|
|
1584
|
+
);
|
|
1585
|
+
} catch (e) {
|
|
1586
|
+
req.flash(
|
|
1587
|
+
"error",
|
|
1588
|
+
e.message || req.__("Error installing module %s", plugin.name)
|
|
1589
|
+
);
|
|
1590
|
+
res.redirect(`/plugins`);
|
|
1591
|
+
return;
|
|
1592
|
+
}
|
|
1497
1593
|
const plugin_module = getState().plugins[name];
|
|
1498
1594
|
await sleep(1000); // Allow other workers to load this plugin
|
|
1499
1595
|
await getState().refresh_views();
|
package/routes/scapi.js
CHANGED
|
@@ -25,6 +25,7 @@ const {
|
|
|
25
25
|
stateFieldsToWhere,
|
|
26
26
|
readState,
|
|
27
27
|
} = require("@saltcorn/data/plugin-helper");
|
|
28
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
28
29
|
|
|
29
30
|
/**
|
|
30
31
|
* @type {object}
|
|
@@ -307,3 +308,21 @@ router.get(
|
|
|
307
308
|
)(req, res, next);
|
|
308
309
|
})
|
|
309
310
|
);
|
|
311
|
+
|
|
312
|
+
router.get(
|
|
313
|
+
"/reload",
|
|
314
|
+
error_catcher(async (req, res, next) => {
|
|
315
|
+
await passport.authenticate(
|
|
316
|
+
"api-bearer",
|
|
317
|
+
{ session: false },
|
|
318
|
+
async function (err, user, info) {
|
|
319
|
+
if (accessAllowedRead(req, user)) {
|
|
320
|
+
await getState().refresh_plugins();
|
|
321
|
+
res.json({ success: true });
|
|
322
|
+
} else {
|
|
323
|
+
res.status(401).json({ error: req.__("Not authorized") });
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
)(req, res, next);
|
|
327
|
+
})
|
|
328
|
+
);
|
package/routes/search.js
CHANGED
|
@@ -202,7 +202,12 @@ const runSearch = async ({ q, _page, table }, req, res) => {
|
|
|
202
202
|
let tablesWithResults = [];
|
|
203
203
|
let tablesConfigured = 0;
|
|
204
204
|
for (const [tableName, viewName] of Object.entries(cfg)) {
|
|
205
|
-
if (
|
|
205
|
+
if (
|
|
206
|
+
!viewName ||
|
|
207
|
+
viewName === "" ||
|
|
208
|
+
viewName === "search_table_description" ||
|
|
209
|
+
tableName === "search_table_description"
|
|
210
|
+
)
|
|
206
211
|
continue;
|
|
207
212
|
tablesConfigured += 1;
|
|
208
213
|
if (table && tableName !== table) continue;
|
package/routes/sync.js
CHANGED
|
@@ -337,13 +337,11 @@ router.post(
|
|
|
337
337
|
const { dir_name } = req.body;
|
|
338
338
|
try {
|
|
339
339
|
const rootFolder = await File.rootFolder();
|
|
340
|
-
const syncDir =
|
|
341
|
-
rootFolder.location,
|
|
342
|
-
"mobile_app",
|
|
343
|
-
"sync",
|
|
340
|
+
const syncDir = File.normalise_in_base(
|
|
341
|
+
path.join(rootFolder.location, "mobile_app", "sync"),
|
|
344
342
|
dir_name
|
|
345
343
|
);
|
|
346
|
-
await fs.rm(syncDir, { recursive: true, force: true });
|
|
344
|
+
if (syncDir) await fs.rm(syncDir, { recursive: true, force: true });
|
|
347
345
|
res.status(200).send("");
|
|
348
346
|
} catch (error) {
|
|
349
347
|
getState().log(2, `POST /sync/clean_sync_dir: '${error.message}'`);
|
package/routes/tables.js
CHANGED
|
@@ -192,6 +192,9 @@ const tableForm = async (table, req) => {
|
|
|
192
192
|
"if(!this.checked && !confirm('Are you sure? This will delete all history')) {this.checked = true; return false}",
|
|
193
193
|
},
|
|
194
194
|
type: "Bool",
|
|
195
|
+
help: {
|
|
196
|
+
topic: "Table history",
|
|
197
|
+
},
|
|
195
198
|
},
|
|
196
199
|
...(table.name === "users"
|
|
197
200
|
? []
|
|
@@ -670,7 +673,10 @@ router.get(
|
|
|
670
673
|
* @param {string} lbl
|
|
671
674
|
* @returns {string}
|
|
672
675
|
*/
|
|
673
|
-
const badge = (col, lbl) =>
|
|
676
|
+
const badge = (col, lbl, title) =>
|
|
677
|
+
`<span ${
|
|
678
|
+
title ? `title="${title}" ` : ""
|
|
679
|
+
}class="badge bg-${col}">${lbl}</span> `;
|
|
674
680
|
|
|
675
681
|
/**
|
|
676
682
|
* @param {object} f
|
|
@@ -708,7 +714,11 @@ const attribBadges = (f) => {
|
|
|
708
714
|
].includes(k)
|
|
709
715
|
)
|
|
710
716
|
return;
|
|
711
|
-
if (v
|
|
717
|
+
if (Array.isArray(v) && !v.length) return;
|
|
718
|
+
const title = ["string", "number", "boolean"].includes(typeof v)
|
|
719
|
+
? `${v}`
|
|
720
|
+
: null;
|
|
721
|
+
if (v || v === 0) s += badge("secondary", k, title);
|
|
712
722
|
});
|
|
713
723
|
}
|
|
714
724
|
return s;
|
|
@@ -791,7 +801,7 @@ router.get(
|
|
|
791
801
|
key: (r) => attribBadges(r),
|
|
792
802
|
},
|
|
793
803
|
{ label: req.__("Variable name"), key: (t) => code(t.name) },
|
|
794
|
-
...(table.external
|
|
804
|
+
...(table.external
|
|
795
805
|
? []
|
|
796
806
|
: [
|
|
797
807
|
{
|
|
@@ -1911,7 +1921,7 @@ router.post(
|
|
|
1911
1921
|
const table = Table.findOne({ name });
|
|
1912
1922
|
|
|
1913
1923
|
try {
|
|
1914
|
-
await table.deleteRows({});
|
|
1924
|
+
await table.deleteRows({}, req.user);
|
|
1915
1925
|
req.flash("success", req.__("Deleted all rows"));
|
|
1916
1926
|
} catch (e) {
|
|
1917
1927
|
req.flash("error", e.message);
|
package/routes/viewedit.js
CHANGED
|
@@ -766,7 +766,8 @@ router.post(
|
|
|
766
766
|
)
|
|
767
767
|
);
|
|
768
768
|
let redirectTarget =
|
|
769
|
-
req.query.on_done_redirect &&
|
|
769
|
+
req.query.on_done_redirect &&
|
|
770
|
+
is_relative_url("/" + req.query.on_done_redirect)
|
|
770
771
|
? `/${req.query.on_done_redirect}`
|
|
771
772
|
: "/viewedit";
|
|
772
773
|
res.redirect(redirectTarget);
|
|
@@ -791,7 +792,8 @@ router.post(
|
|
|
791
792
|
req.__("View %s duplicated as %s", view.name, newview.name)
|
|
792
793
|
);
|
|
793
794
|
let redirectTarget =
|
|
794
|
-
req.query.on_done_redirect &&
|
|
795
|
+
req.query.on_done_redirect &&
|
|
796
|
+
is_relative_url("/" + req.query.on_done_redirect)
|
|
795
797
|
? `/${req.query.on_done_redirect}`
|
|
796
798
|
: "/viewedit";
|
|
797
799
|
res.redirect(redirectTarget);
|
|
@@ -812,7 +814,8 @@ router.post(
|
|
|
812
814
|
await View.delete({ id });
|
|
813
815
|
req.flash("success", req.__("View deleted"));
|
|
814
816
|
let redirectTarget =
|
|
815
|
-
req.query.on_done_redirect &&
|
|
817
|
+
req.query.on_done_redirect &&
|
|
818
|
+
is_relative_url("/" + req.query.on_done_redirect)
|
|
816
819
|
? `/${req.query.on_done_redirect}`
|
|
817
820
|
: "/viewedit";
|
|
818
821
|
res.redirect(redirectTarget);
|
|
@@ -910,7 +913,7 @@ router.post(
|
|
|
910
913
|
req.flash("success", message);
|
|
911
914
|
let redirectTarget =
|
|
912
915
|
req.query.on_done_redirect &&
|
|
913
|
-
is_relative_url(req.query.on_done_redirect)
|
|
916
|
+
is_relative_url("/" + req.query.on_done_redirect)
|
|
914
917
|
? `/${req.query.on_done_redirect}`
|
|
915
918
|
: "/viewedit";
|
|
916
919
|
res.redirect(redirectTarget);
|
package/serve.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* @category server
|
|
5
5
|
* @module serve
|
|
6
6
|
*/
|
|
7
|
-
const runScheduler = require("@saltcorn/data/models/scheduler");
|
|
7
|
+
const { runScheduler } = require("@saltcorn/data/models/scheduler");
|
|
8
8
|
const User = require("@saltcorn/data/models/user");
|
|
9
9
|
const Plugin = require("@saltcorn/data/models/plugin");
|
|
10
10
|
const db = require("@saltcorn/data/db");
|
|
@@ -27,7 +27,7 @@ const {
|
|
|
27
27
|
loadAndSaveNewPlugin,
|
|
28
28
|
loadPlugin,
|
|
29
29
|
} = require("./load_plugins");
|
|
30
|
-
const { getConfig } = require("@saltcorn/data/models/config");
|
|
30
|
+
const { getConfig, setConfig } = require("@saltcorn/data/models/config");
|
|
31
31
|
const { migrate } = require("@saltcorn/data/migrate");
|
|
32
32
|
const socketio = require("socket.io");
|
|
33
33
|
const { createAdapter, setupPrimary } = require("@socket.io/cluster-adapter");
|
|
@@ -77,6 +77,17 @@ const ensureJwtSecret = () => {
|
|
|
77
77
|
}
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Ensure the engines cache is up to date with the current sc version
|
|
82
|
+
*/
|
|
83
|
+
const ensureEnginesCache = async () => {
|
|
84
|
+
const cacheScVersion = await getConfig("engines_cache_sc_version", "");
|
|
85
|
+
if (!cacheScVersion || cacheScVersion !== getState().scVersion) {
|
|
86
|
+
await setConfig("engines_cache", {});
|
|
87
|
+
await setConfig("engines_cache_sc_version", getState().scVersion);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
80
91
|
// helpful https://gist.github.com/jpoehls/2232358
|
|
81
92
|
/**
|
|
82
93
|
* @param {object} opts
|
|
@@ -222,7 +233,10 @@ module.exports =
|
|
|
222
233
|
dev,
|
|
223
234
|
...appargs
|
|
224
235
|
} = {}) => {
|
|
225
|
-
|
|
236
|
+
if (cluster.isMaster) {
|
|
237
|
+
ensureJwtSecret();
|
|
238
|
+
await ensureEnginesCache();
|
|
239
|
+
}
|
|
226
240
|
process.on("unhandledRejection", (reason, p) => {
|
|
227
241
|
console.error(reason, "Unhandled Rejection at Promise");
|
|
228
242
|
});
|
package/tests/api.test.js
CHANGED
|
@@ -2,6 +2,8 @@ const request = require("supertest");
|
|
|
2
2
|
const getApp = require("../app");
|
|
3
3
|
const Table = require("@saltcorn/data/models/table");
|
|
4
4
|
const Trigger = require("@saltcorn/data/models/trigger");
|
|
5
|
+
const File = require("@saltcorn/data/models/file");
|
|
6
|
+
const fs = require("fs").promises;
|
|
5
7
|
|
|
6
8
|
const Field = require("@saltcorn/data/models/field");
|
|
7
9
|
const {
|
|
@@ -19,6 +21,19 @@ const User = require("@saltcorn/data/models/user");
|
|
|
19
21
|
|
|
20
22
|
beforeAll(async () => {
|
|
21
23
|
await resetToFixtures();
|
|
24
|
+
await File.ensure_file_store();
|
|
25
|
+
await File.from_req_files(
|
|
26
|
+
{
|
|
27
|
+
mimetype: "image/png",
|
|
28
|
+
name: "rick1.png",
|
|
29
|
+
mv: async (fnm) => {
|
|
30
|
+
await fs.writeFile(fnm, "nevergonnagiveyouup");
|
|
31
|
+
},
|
|
32
|
+
size: 245752,
|
|
33
|
+
},
|
|
34
|
+
1,
|
|
35
|
+
80
|
|
36
|
+
);
|
|
22
37
|
});
|
|
23
38
|
afterAll(db.close);
|
|
24
39
|
|
|
@@ -352,6 +367,20 @@ describe("API authentication", () => {
|
|
|
352
367
|
|
|
353
368
|
.expect(succeedJsonWith((rows) => rows.length == 2));
|
|
354
369
|
});
|
|
370
|
+
it("should not show file to public", async () => {
|
|
371
|
+
const app = await getApp();
|
|
372
|
+
await request(app)
|
|
373
|
+
.get("/api/serve-files/rick1.png")
|
|
374
|
+
.expect(respondJsonWith(404, (b) => b.error === "Not found"));
|
|
375
|
+
});
|
|
376
|
+
it("should show file to user", async () => {
|
|
377
|
+
const app = await getApp();
|
|
378
|
+
const u = await User.findOne({ id: 1 });
|
|
379
|
+
await request(app)
|
|
380
|
+
.get("/api/serve-files/rick1.png")
|
|
381
|
+
.set("Authorization", "Bearer " + u.api_token)
|
|
382
|
+
.expect(200);
|
|
383
|
+
});
|
|
355
384
|
});
|
|
356
385
|
|
|
357
386
|
describe("API action", () => {
|