@saltcorn/server 0.8.7-beta.3 → 0.8.7-beta.5
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/auth/admin.js +3 -3
- package/locales/en.json +17 -1
- package/locales/no.json +57 -57
- package/locales/ru.json +49 -8
- package/markup/admin.js +1 -1
- package/markup/forms.js +8 -2
- package/package.json +8 -8
- package/public/cytoscape-popper.min.js +1 -0
- package/public/cytoscape.min.js +32 -0
- package/public/mermaid.min.js +1630 -0
- package/public/popper.min.js +6 -0
- package/public/relationship_diagram_utils.js +75 -0
- package/public/saltcorn-common.js +11 -0
- package/public/saltcorn.css +45 -0
- package/public/saltcorn.js +18 -2
- package/routes/actions.js +6 -6
- package/routes/admin.js +144 -41
- package/routes/common_lists.js +1 -1
- package/routes/diagram.js +4 -6
- package/routes/fields.js +3 -3
- package/routes/files.js +21 -21
- package/routes/list.js +1 -9
- package/routes/notifications.js +1 -1
- package/routes/plugins.js +4 -3
- package/routes/tables.js +127 -48
- package/tests/clientjs.test.js +3 -0
- package/tests/files.test.js +123 -2
package/routes/diagram.js
CHANGED
|
@@ -22,6 +22,7 @@ const { isAdmin, error_catcher } = require("./utils.js");
|
|
|
22
22
|
const Tag = require("@saltcorn/data/models/tag");
|
|
23
23
|
const Router = require("express-promise-router");
|
|
24
24
|
const User = require("@saltcorn/data/models/user");
|
|
25
|
+
const db = require("@saltcorn/data/db");
|
|
25
26
|
|
|
26
27
|
const router = new Router();
|
|
27
28
|
module.exports = router;
|
|
@@ -343,16 +344,13 @@ router.get(
|
|
|
343
344
|
}`,
|
|
344
345
|
},
|
|
345
346
|
{
|
|
346
|
-
script:
|
|
347
|
-
"https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.9.2/umd/popper.min.js",
|
|
347
|
+
script: `/static_assets/${db.connectObj.version_tag}/popper.min.js`,
|
|
348
348
|
},
|
|
349
349
|
{
|
|
350
|
-
script:
|
|
351
|
-
"https://cdnjs.cloudflare.com/ajax/libs/cytoscape/3.22.1/cytoscape.min.js",
|
|
350
|
+
script: `/static_assets/${db.connectObj.version_tag}/cytoscape.min.js`,
|
|
352
351
|
},
|
|
353
352
|
{
|
|
354
|
-
script:
|
|
355
|
-
"https://cdnjs.cloudflare.com/ajax/libs/cytoscape-popper/2.0.0/cytoscape-popper.min.js",
|
|
353
|
+
script: `/static_assets/${db.connectObj.version_tag}/cytoscape-popper.min.js`,
|
|
356
354
|
},
|
|
357
355
|
],
|
|
358
356
|
});
|
package/routes/fields.js
CHANGED
|
@@ -454,11 +454,11 @@ const fieldFlow = (req) =>
|
|
|
454
454
|
required: true,
|
|
455
455
|
attributes: {
|
|
456
456
|
explainers: {
|
|
457
|
-
Fail: "Prevent any deletion of parent rows",
|
|
457
|
+
Fail: req.__("Prevent any deletion of parent rows"),
|
|
458
458
|
Cascade:
|
|
459
|
-
|
|
459
|
+
req.__("If the parent row is deleted, automatically delete the child rows."),
|
|
460
460
|
"Set null":
|
|
461
|
-
|
|
461
|
+
req.__("If the parent row is deleted, set key fields on child rows to null"),
|
|
462
462
|
},
|
|
463
463
|
},
|
|
464
464
|
sublabel: req.__(
|
package/routes/files.js
CHANGED
|
@@ -69,9 +69,12 @@ router.get(
|
|
|
69
69
|
isAdmin,
|
|
70
70
|
error_catcher(async (req, res) => {
|
|
71
71
|
// todo limit select from file by 10 or 20
|
|
72
|
-
const { dir } = req.query;
|
|
72
|
+
const { dir, search } = req.query;
|
|
73
73
|
const safeDir = File.normalise(dir || "/");
|
|
74
|
-
const rows = await File.find(
|
|
74
|
+
const rows = await File.find(
|
|
75
|
+
{ folder: dir, search },
|
|
76
|
+
{ orderBy: "filename" }
|
|
77
|
+
);
|
|
75
78
|
const roles = await User.get_roles();
|
|
76
79
|
if (safeDir && safeDir !== "/" && safeDir !== ".") {
|
|
77
80
|
let dirname = path.dirname(safeDir);
|
|
@@ -383,7 +386,7 @@ router.post(
|
|
|
383
386
|
"/upload",
|
|
384
387
|
setTenant,
|
|
385
388
|
error_catcher(async (req, res) => {
|
|
386
|
-
let { folder } = req.body;
|
|
389
|
+
let { folder, sortBy, sortDesc } = req.body;
|
|
387
390
|
let jsonResp = {};
|
|
388
391
|
const min_role_upload = getState().getConfig("min_role_upload", 1);
|
|
389
392
|
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
@@ -404,16 +407,11 @@ router.post(
|
|
|
404
407
|
);
|
|
405
408
|
const many = Array.isArray(f);
|
|
406
409
|
file_for_redirect = many ? f[0] : f;
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
many
|
|
413
|
-
? f.map((fl) => text(fl.filename)).join(", ")
|
|
414
|
-
: text(f.filename)
|
|
415
|
-
)
|
|
416
|
-
);
|
|
410
|
+
const successMsg = req.__(
|
|
411
|
+
`File %s uploaded`,
|
|
412
|
+
many ? f.map((fl) => text(fl.filename)).join(", ") : text(f.filename)
|
|
413
|
+
);
|
|
414
|
+
if (!req.xhr) req.flash("success", successMsg);
|
|
417
415
|
else
|
|
418
416
|
jsonResp = {
|
|
419
417
|
success: {
|
|
@@ -422,16 +420,18 @@ router.post(
|
|
|
422
420
|
url: many
|
|
423
421
|
? f.map((fl) => `/files/serve/${fl.path_to_serve}`)
|
|
424
422
|
: `/files/serve/${f.path_to_serve}`,
|
|
423
|
+
msg: successMsg,
|
|
425
424
|
},
|
|
426
425
|
};
|
|
427
426
|
}
|
|
428
|
-
if (!req.xhr)
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
);
|
|
434
|
-
|
|
427
|
+
if (!req.xhr) {
|
|
428
|
+
const sp = new URLSearchParams();
|
|
429
|
+
if (file_for_redirect) sp.append("dir", file_for_redirect.current_folder);
|
|
430
|
+
if (sortBy) sp.append("sortBy", sortBy);
|
|
431
|
+
if (sortDesc) sp.append("sortDesc", sortDesc);
|
|
432
|
+
const query = sp.toString();
|
|
433
|
+
res.redirect(`/files${query ? `?${query}` : ""}`);
|
|
434
|
+
} else res.json(jsonResp);
|
|
435
435
|
})
|
|
436
436
|
);
|
|
437
437
|
|
|
@@ -449,7 +449,7 @@ router.post(
|
|
|
449
449
|
const { redirect } = req.query;
|
|
450
450
|
const f = await File.findOne(serve_path);
|
|
451
451
|
if (!f) {
|
|
452
|
-
req.flash("error", "File not found");
|
|
452
|
+
req.flash("error", req.__("File not found"));
|
|
453
453
|
res.redirect("/files");
|
|
454
454
|
return;
|
|
455
455
|
}
|
package/routes/list.js
CHANGED
|
@@ -100,15 +100,7 @@ router.post(
|
|
|
100
100
|
error_catcher(async (req, res) => {
|
|
101
101
|
const { tableName, id, _version } = req.params;
|
|
102
102
|
const table = Table.findOne({ name: tableName });
|
|
103
|
-
|
|
104
|
-
const fields = table.getFields();
|
|
105
|
-
const row = await db.selectOne(`${db.sqlsanitize(table.name)}__history`, {
|
|
106
|
-
id,
|
|
107
|
-
_version,
|
|
108
|
-
});
|
|
109
|
-
var r = {};
|
|
110
|
-
fields.forEach((f) => (r[f.name] = row[f.name]));
|
|
111
|
-
await table.updateRow(r, +id);
|
|
103
|
+
await table.restore_row_version(id, _version);
|
|
112
104
|
req.flash("success", req.__("Version %s restored", _version));
|
|
113
105
|
res.redirect(`/list/_versions/${table.name}/${id}`);
|
|
114
106
|
})
|
package/routes/notifications.js
CHANGED
package/routes/plugins.js
CHANGED
|
@@ -526,7 +526,8 @@ const plugin_store_html = (items, req) => {
|
|
|
526
526
|
contents: div(
|
|
527
527
|
{ class: "d-flex justify-content-between" },
|
|
528
528
|
storeNavPills(req),
|
|
529
|
-
div(search_bar("q", req.query.q || "",
|
|
529
|
+
div(search_bar("q", req.query.q || "",
|
|
530
|
+
{ placeHolder: req.__("Search for..."), stateField: "q" })),
|
|
530
531
|
div(store_actions_dropdown(req))
|
|
531
532
|
),
|
|
532
533
|
},
|
|
@@ -825,7 +826,7 @@ router.get(
|
|
|
825
826
|
pkgjson = require(path.join(mod.location, "package.json"));
|
|
826
827
|
|
|
827
828
|
if (!plugin_db) {
|
|
828
|
-
req.flash("warning", "Module not found");
|
|
829
|
+
req.flash("warning", req.__("Module not found"));
|
|
829
830
|
res.redirect("/plugins");
|
|
830
831
|
return;
|
|
831
832
|
}
|
|
@@ -1035,7 +1036,7 @@ router.post(
|
|
|
1035
1036
|
|
|
1036
1037
|
const plugin = await Plugin.findOne({ name: decodeURIComponent(name) });
|
|
1037
1038
|
if (!plugin) {
|
|
1038
|
-
req.flash("warning", "Module not found");
|
|
1039
|
+
req.flash("warning", req.__("Module not found"));
|
|
1039
1040
|
res.redirect("/plugins");
|
|
1040
1041
|
return;
|
|
1041
1042
|
}
|
package/routes/tables.js
CHANGED
|
@@ -42,6 +42,7 @@ const {
|
|
|
42
42
|
domReady,
|
|
43
43
|
code,
|
|
44
44
|
pre,
|
|
45
|
+
button,
|
|
45
46
|
} = require("@saltcorn/markup/tags");
|
|
46
47
|
const stringify = require("csv-stringify");
|
|
47
48
|
const TableConstraint = require("@saltcorn/data/models/table_constraints");
|
|
@@ -54,8 +55,11 @@ const {
|
|
|
54
55
|
const { getState } = require("@saltcorn/data/db/state");
|
|
55
56
|
const { cardHeaderTabs } = require("@saltcorn/markup/layout_utils");
|
|
56
57
|
const { tablesList } = require("./common_lists");
|
|
57
|
-
const {
|
|
58
|
-
|
|
58
|
+
const {
|
|
59
|
+
InvalidConfiguration,
|
|
60
|
+
removeAllWhiteSpace,
|
|
61
|
+
} = require("@saltcorn/data/utils");
|
|
62
|
+
const { EOL } = require("os");
|
|
59
63
|
|
|
60
64
|
const path = require("path");
|
|
61
65
|
/**
|
|
@@ -421,6 +425,101 @@ router.post(
|
|
|
421
425
|
})
|
|
422
426
|
);
|
|
423
427
|
|
|
428
|
+
const indentString = (str, indent) => `${" ".repeat(indent)}${str}`;
|
|
429
|
+
|
|
430
|
+
const srcCardinality = (field) => (field.required ? "||" : "|o");
|
|
431
|
+
|
|
432
|
+
const buildTableMarkup = (table) => {
|
|
433
|
+
const fields = table.getFields();
|
|
434
|
+
const members = fields
|
|
435
|
+
// .filter((f) => !f.reftable_name)
|
|
436
|
+
.map((f) =>
|
|
437
|
+
indentString(`${removeAllWhiteSpace(f.type_name)} ${f.name}`, 6)
|
|
438
|
+
)
|
|
439
|
+
.join(EOL);
|
|
440
|
+
const keys = table
|
|
441
|
+
.getForeignKeys()
|
|
442
|
+
.map((f) =>
|
|
443
|
+
indentString(
|
|
444
|
+
`"${table.name}"${srcCardinality(f)}--|| "${f.reftable_name}" : "${
|
|
445
|
+
f.name
|
|
446
|
+
}"`,
|
|
447
|
+
2
|
|
448
|
+
)
|
|
449
|
+
)
|
|
450
|
+
.join(EOL);
|
|
451
|
+
return `${keys}
|
|
452
|
+
"${table.name}" {${EOL}${members}${EOL} }`;
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
const buildMermaidMarkup = (tables) => {
|
|
456
|
+
const lines = tables.map((table) => buildTableMarkup(table)).join(EOL);
|
|
457
|
+
return `${indentString("erDiagram", 2)}${EOL}${lines}`;
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
const navigationPanel = () =>
|
|
461
|
+
div(
|
|
462
|
+
{ class: "er-navigation-panel" },
|
|
463
|
+
button(
|
|
464
|
+
{
|
|
465
|
+
class: "btn btn-primary er-up",
|
|
466
|
+
onclick: "erHelper.translateY(-100)",
|
|
467
|
+
},
|
|
468
|
+
i({ class: "fas fa-chevron-up" })
|
|
469
|
+
),
|
|
470
|
+
button(
|
|
471
|
+
{
|
|
472
|
+
class: "btn btn-primary er-zoom-in",
|
|
473
|
+
onclick: "erHelper.zoom(0.1)",
|
|
474
|
+
},
|
|
475
|
+
i({ class: "fas fa-search-plus" })
|
|
476
|
+
),
|
|
477
|
+
button(
|
|
478
|
+
{
|
|
479
|
+
class: "btn btn-primary er-left",
|
|
480
|
+
onclick: "erHelper.translateX(-100)",
|
|
481
|
+
},
|
|
482
|
+
i({ class: "fas fa-chevron-left" })
|
|
483
|
+
),
|
|
484
|
+
button(
|
|
485
|
+
{ class: "btn btn-primary er-reset", onclick: "erHelper.reset()" },
|
|
486
|
+
i({ class: "fas fa-sync-alt" })
|
|
487
|
+
),
|
|
488
|
+
button(
|
|
489
|
+
{
|
|
490
|
+
class: "btn btn-primary er-right",
|
|
491
|
+
onclick: "erHelper.translateX(100)",
|
|
492
|
+
},
|
|
493
|
+
i({ class: "fas fa-chevron-right" })
|
|
494
|
+
),
|
|
495
|
+
button(
|
|
496
|
+
{
|
|
497
|
+
class: "btn btn-primary er-down",
|
|
498
|
+
onclick: "erHelper.translateY(100)",
|
|
499
|
+
},
|
|
500
|
+
i({ class: "fas fa-chevron-down" })
|
|
501
|
+
),
|
|
502
|
+
button(
|
|
503
|
+
{
|
|
504
|
+
class: "btn btn-primary er-zoom-out",
|
|
505
|
+
onclick: "erHelper.zoom(-0.1)",
|
|
506
|
+
},
|
|
507
|
+
i({ class: "fas fa-search-minus" })
|
|
508
|
+
)
|
|
509
|
+
);
|
|
510
|
+
|
|
511
|
+
const screenshotPanel = () =>
|
|
512
|
+
div(
|
|
513
|
+
{ class: "er-screenshot-panel" },
|
|
514
|
+
button(
|
|
515
|
+
{
|
|
516
|
+
class: "btn btn-primary",
|
|
517
|
+
onclick: "erHelper.takePicture()",
|
|
518
|
+
},
|
|
519
|
+
i({ class: "fas fa-camera" })
|
|
520
|
+
)
|
|
521
|
+
);
|
|
522
|
+
|
|
424
523
|
/**
|
|
425
524
|
* Show Relational Diagram (get)
|
|
426
525
|
* @name get/relationship-diagram
|
|
@@ -433,34 +532,24 @@ router.get(
|
|
|
433
532
|
isAdmin,
|
|
434
533
|
error_catcher(async (req, res) => {
|
|
435
534
|
const tables = await Table.find_with_external({}, { orderBy: "name" });
|
|
436
|
-
const edges = [];
|
|
437
|
-
for (const table of tables) {
|
|
438
|
-
const fields = table.getFields();
|
|
439
|
-
for (const field of fields) {
|
|
440
|
-
if (field.reftable_name)
|
|
441
|
-
edges.push({
|
|
442
|
-
from: table.name,
|
|
443
|
-
to: field.reftable_name,
|
|
444
|
-
arrows: "to",
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
const data = {
|
|
449
|
-
nodes: tables.map((t) => ({
|
|
450
|
-
id: t.name,
|
|
451
|
-
label: `<b>${t.name}</b>\n${t.fields
|
|
452
|
-
.map((f) => `${f.name} : ${f.pretty_type}`)
|
|
453
|
-
.join("\n")}`,
|
|
454
|
-
title: t.description ? t.description : t.name,
|
|
455
|
-
})),
|
|
456
|
-
edges,
|
|
457
|
-
};
|
|
458
535
|
res.sendWrap(
|
|
459
536
|
{
|
|
460
537
|
title: req.__("Tables"),
|
|
461
538
|
headers: [
|
|
462
539
|
{
|
|
463
|
-
script: `/static_assets/${db.connectObj.version_tag}/
|
|
540
|
+
script: `/static_assets/${db.connectObj.version_tag}/mermaid.min.js`,
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
headerTag: `
|
|
544
|
+
<script type="module">
|
|
545
|
+
mermaid.initialize({ startOnLoad: false });
|
|
546
|
+
await mermaid.run({
|
|
547
|
+
querySelector: ".mermaid",
|
|
548
|
+
postRenderCallback: (id) => {
|
|
549
|
+
$("#" + id).css("height", "calc(100vh - 250px)");
|
|
550
|
+
}
|
|
551
|
+
});
|
|
552
|
+
</script>`,
|
|
464
553
|
},
|
|
465
554
|
],
|
|
466
555
|
},
|
|
@@ -482,29 +571,19 @@ router.get(
|
|
|
482
571
|
},
|
|
483
572
|
]),
|
|
484
573
|
contents: [
|
|
485
|
-
div(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
physics: {
|
|
497
|
-
// Even though it's disabled the options still apply to network.stabilize().
|
|
498
|
-
enabled: false,
|
|
499
|
-
solver: "repulsion",
|
|
500
|
-
repulsion: {
|
|
501
|
-
nodeDistance: 100 // Put more distance between the nodes.
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
};
|
|
505
|
-
var network = new vis.Network(container, data, options);
|
|
506
|
-
network.stabilize();`)
|
|
574
|
+
div(
|
|
575
|
+
{
|
|
576
|
+
style: "height: calc(100vh - 250px);",
|
|
577
|
+
class: "overflow-scroll position-relative",
|
|
578
|
+
},
|
|
579
|
+
screenshotPanel(),
|
|
580
|
+
pre(
|
|
581
|
+
{ class: "mermaid", style: "height: calc(100vh - 250px);" },
|
|
582
|
+
buildMermaidMarkup(tables)
|
|
583
|
+
),
|
|
584
|
+
navigationPanel()
|
|
507
585
|
),
|
|
586
|
+
script({ src: "/relationship_diagram_utils.js" }),
|
|
508
587
|
],
|
|
509
588
|
},
|
|
510
589
|
],
|
|
@@ -1135,7 +1214,7 @@ router.get(
|
|
|
1135
1214
|
error_catcher(async (req, res) => {
|
|
1136
1215
|
const { name } = req.params;
|
|
1137
1216
|
const table = Table.findOne({ name });
|
|
1138
|
-
const rows = await table.getRows({}, { orderBy: "id"
|
|
1217
|
+
const rows = await table.getRows({}, { orderBy: "id" });
|
|
1139
1218
|
res.setHeader("Content-Type", "text/csv");
|
|
1140
1219
|
res.setHeader("Content-Disposition", `attachment; filename="${name}.csv"`);
|
|
1141
1220
|
res.setHeader("Cache-Control", "no-cache");
|
package/tests/clientjs.test.js
CHANGED
|
@@ -34,6 +34,9 @@ test("updateQueryStringParameter", () => {
|
|
|
34
34
|
expect(removeQueryStringParameter("/foo?name=Bar&age=45", "age")).toBe(
|
|
35
35
|
"/foo?name=Bar"
|
|
36
36
|
);
|
|
37
|
+
expect(
|
|
38
|
+
removeQueryStringParameter("/foo?name=Baz&name=Foo&age=45", "name")
|
|
39
|
+
).not.toContain("name");
|
|
37
40
|
expect(
|
|
38
41
|
updateQueryStringParameter("/foo", "publisher.publisher->name", "AK")
|
|
39
42
|
).toBe("/foo?publisher.publisher->name=AK");
|
package/tests/files.test.js
CHANGED
|
@@ -7,9 +7,9 @@ const {
|
|
|
7
7
|
itShouldRedirectUnauthToLogin,
|
|
8
8
|
toInclude,
|
|
9
9
|
toSucceed,
|
|
10
|
-
toNotInclude,
|
|
11
10
|
resetToFixtures,
|
|
12
11
|
toSucceedWithImage,
|
|
12
|
+
respondJsonWith,
|
|
13
13
|
} = require("../auth/testhelp");
|
|
14
14
|
const db = require("@saltcorn/data/db");
|
|
15
15
|
const fs = require("fs").promises;
|
|
@@ -18,7 +18,17 @@ const File = require("@saltcorn/data/models/file");
|
|
|
18
18
|
const Field = require("@saltcorn/data/models/field");
|
|
19
19
|
const Table = require("@saltcorn/data/models/table");
|
|
20
20
|
const View = require("@saltcorn/data/models/view");
|
|
21
|
-
const {
|
|
21
|
+
const { existsSync } = require("fs");
|
|
22
|
+
|
|
23
|
+
const createTestFile = async (folder, name, mimetype, content) => {
|
|
24
|
+
if (
|
|
25
|
+
!existsSync(
|
|
26
|
+
path.join(db.connectObj.file_store, db.getTenantSchema(), folder, name)
|
|
27
|
+
)
|
|
28
|
+
) {
|
|
29
|
+
await File.from_contents(name, mimetype, content, 1, 100, folder);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
22
32
|
|
|
23
33
|
beforeAll(async () => {
|
|
24
34
|
await resetToFixtures();
|
|
@@ -47,6 +57,33 @@ beforeAll(async () => {
|
|
|
47
57
|
1,
|
|
48
58
|
100
|
|
49
59
|
);
|
|
60
|
+
|
|
61
|
+
await File.new_folder(path.join("_sc_test_subfolder_one", "subsubfolder"));
|
|
62
|
+
await createTestFile(
|
|
63
|
+
"_sc_test_subfolder_one",
|
|
64
|
+
"foo_image.png",
|
|
65
|
+
"image/png",
|
|
66
|
+
"imagecontent"
|
|
67
|
+
);
|
|
68
|
+
await createTestFile(
|
|
69
|
+
"_sc_test_subfolder_one",
|
|
70
|
+
"bar_image.png",
|
|
71
|
+
"image/png",
|
|
72
|
+
"imagecontent"
|
|
73
|
+
);
|
|
74
|
+
await createTestFile(
|
|
75
|
+
path.join("_sc_test_subfolder_one", "subsubfolder"),
|
|
76
|
+
"bar_image.png",
|
|
77
|
+
"image/png",
|
|
78
|
+
"imagecontent"
|
|
79
|
+
);
|
|
80
|
+
await File.new_folder("_sc_test_subfolder_two");
|
|
81
|
+
await createTestFile(
|
|
82
|
+
"_sc_test_subfolder_two",
|
|
83
|
+
"foo_image.png",
|
|
84
|
+
"image/png",
|
|
85
|
+
"imagecontent"
|
|
86
|
+
);
|
|
50
87
|
});
|
|
51
88
|
afterAll(db.close);
|
|
52
89
|
|
|
@@ -146,6 +183,90 @@ describe("files admin", () => {
|
|
|
146
183
|
|
|
147
184
|
.expect(toRedirect("/files?dir=."));
|
|
148
185
|
});
|
|
186
|
+
it("search files by name", async () => {
|
|
187
|
+
const app = await getApp({ disableCsrf: true });
|
|
188
|
+
const loginCookie = await getAdminLoginCookie();
|
|
189
|
+
const checkFiles = (files, expecteds) =>
|
|
190
|
+
files.length === expecteds.length &&
|
|
191
|
+
expecteds.every(({ filename, location }) =>
|
|
192
|
+
files.find(
|
|
193
|
+
(file) => file.filename === filename && file.location === location
|
|
194
|
+
)
|
|
195
|
+
);
|
|
196
|
+
const searchTestHelper = async (dir, search, expected) => {
|
|
197
|
+
await request(app)
|
|
198
|
+
.get("/files")
|
|
199
|
+
.query({ dir, search })
|
|
200
|
+
.set("X-Requested-With", "XMLHttpRequest")
|
|
201
|
+
.set("Cookie", loginCookie)
|
|
202
|
+
.expect(
|
|
203
|
+
respondJsonWith(200, (data) => checkFiles(data.files, expected))
|
|
204
|
+
);
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
await searchTestHelper("/", "foo", [
|
|
208
|
+
{
|
|
209
|
+
filename: "foo_image.png",
|
|
210
|
+
location: path.join("_sc_test_subfolder_one", "foo_image.png"),
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
filename: "foo_image.png",
|
|
214
|
+
location: path.join("_sc_test_subfolder_two", "foo_image.png"),
|
|
215
|
+
},
|
|
216
|
+
]);
|
|
217
|
+
await searchTestHelper("_sc_test_subfolder_two", "foo", [
|
|
218
|
+
{
|
|
219
|
+
filename: "foo_image.png",
|
|
220
|
+
location: path.join("_sc_test_subfolder_two", "foo_image.png"),
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
filename: "..",
|
|
224
|
+
location: "",
|
|
225
|
+
},
|
|
226
|
+
]);
|
|
227
|
+
await searchTestHelper("/", "bar", [
|
|
228
|
+
{
|
|
229
|
+
filename: "bar_image.png",
|
|
230
|
+
location: path.join("_sc_test_subfolder_one", "bar_image.png"),
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
filename: "bar_image.png",
|
|
234
|
+
location: path.join(
|
|
235
|
+
"_sc_test_subfolder_one",
|
|
236
|
+
"subsubfolder",
|
|
237
|
+
"bar_image.png"
|
|
238
|
+
),
|
|
239
|
+
},
|
|
240
|
+
]);
|
|
241
|
+
await searchTestHelper(
|
|
242
|
+
path.join("_sc_test_subfolder_one", "subsubfolder"),
|
|
243
|
+
"foo",
|
|
244
|
+
[
|
|
245
|
+
{
|
|
246
|
+
filename: "..",
|
|
247
|
+
location: "_sc_test_subfolder_one",
|
|
248
|
+
},
|
|
249
|
+
]
|
|
250
|
+
);
|
|
251
|
+
await searchTestHelper(
|
|
252
|
+
path.join("_sc_test_subfolder_one", "subsubfolder"),
|
|
253
|
+
"bar",
|
|
254
|
+
[
|
|
255
|
+
{
|
|
256
|
+
filename: "..",
|
|
257
|
+
location: "_sc_test_subfolder_one",
|
|
258
|
+
},
|
|
259
|
+
{
|
|
260
|
+
filename: "bar_image.png",
|
|
261
|
+
location: path.join(
|
|
262
|
+
"_sc_test_subfolder_one",
|
|
263
|
+
"subsubfolder",
|
|
264
|
+
"bar_image.png"
|
|
265
|
+
),
|
|
266
|
+
},
|
|
267
|
+
]
|
|
268
|
+
);
|
|
269
|
+
});
|
|
149
270
|
});
|
|
150
271
|
describe("files edit", () => {
|
|
151
272
|
it("creates table and view", async () => {
|