@saltcorn/server 0.8.6-beta.1 → 0.8.6-beta.3
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 +16 -3
- package/locales/pl.json +1 -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 +46 -35
- package/public/saltcorn.js +2 -2
- package/routes/admin.js +23 -0
- package/routes/api.js +21 -20
- package/routes/common_lists.js +4 -1
- package/routes/delete.js +3 -3
- package/routes/edit.js +1 -1
- package/routes/fields.js +8 -1
- package/routes/files.js +7 -7
- 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 +170 -0
- package/routes/tables.js +4 -0
- package/routes/tenant.js +74 -27
- package/routes/view.js +3 -3
- package/routes/viewedit.js +24 -11
- 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 +187 -0
- package/tests/table.test.js +6 -6
- 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/sync.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
const { error_catcher } = require("./utils.js");
|
|
2
|
+
const Table = require("@saltcorn/data/models/table");
|
|
3
|
+
const Router = require("express-promise-router");
|
|
4
|
+
const db = require("@saltcorn/data/db");
|
|
5
|
+
const { getState } = require("@saltcorn/data/db/state");
|
|
6
|
+
|
|
7
|
+
const router = new Router();
|
|
8
|
+
module.exports = router;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Send all rows from a user, so that they can be used in an offline session with the mobile app
|
|
12
|
+
*/
|
|
13
|
+
router.get(
|
|
14
|
+
"/table_data",
|
|
15
|
+
error_catcher(async (req, res) => {
|
|
16
|
+
// TODO optimsie: hash over all rows or dynamic user specific
|
|
17
|
+
// TODO public user
|
|
18
|
+
// TODO split large data 10 000 rows?
|
|
19
|
+
getState().log(
|
|
20
|
+
4,
|
|
21
|
+
`GET /sync/table_data user: '${req.user ? req.user.id : "public"}'`
|
|
22
|
+
);
|
|
23
|
+
const allTables = await Table.find();
|
|
24
|
+
const result = {};
|
|
25
|
+
const selectOpts = req.user ? { forUser: req.user } : { forPublic: true };
|
|
26
|
+
for (const table of allTables) {
|
|
27
|
+
const rows = await table.getRows({}, selectOpts);
|
|
28
|
+
if (
|
|
29
|
+
req.user &&
|
|
30
|
+
table.name === "users" &&
|
|
31
|
+
!rows.find((row) => row.id === req.user.id)
|
|
32
|
+
) {
|
|
33
|
+
rows.push(await table.getRow({ id: req.user.id }));
|
|
34
|
+
}
|
|
35
|
+
result[table.name] = {
|
|
36
|
+
rows:
|
|
37
|
+
table.name !== "users"
|
|
38
|
+
? rows
|
|
39
|
+
: rows.map(({ id, email, role_id, language, disabled }) => {
|
|
40
|
+
return { id, email, role_id, language, disabled };
|
|
41
|
+
}),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
res.json(result);
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const pickFields = (table, row) => {
|
|
49
|
+
const result = {};
|
|
50
|
+
for (const { name, type } of table.getFields()) {
|
|
51
|
+
if (type?.name === "Date") {
|
|
52
|
+
result[name] = row[name] ? new Date(row[name]) : undefined;
|
|
53
|
+
} else {
|
|
54
|
+
result[name] = row[name];
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return result;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const getChanges = (table, dbRow, appRow) => {
|
|
61
|
+
const changes = {};
|
|
62
|
+
for (const { name, type } of table.getFields()) {
|
|
63
|
+
if (name !== "id") {
|
|
64
|
+
const dbVal = dbRow[name];
|
|
65
|
+
const appVal = appRow[name];
|
|
66
|
+
let valHasChanged = false;
|
|
67
|
+
if (type?.name === "Date") {
|
|
68
|
+
valHasChanged = dbVal?.valueOf() !== appVal?.valueOf();
|
|
69
|
+
} else {
|
|
70
|
+
valHasChanged = dbVal !== appVal;
|
|
71
|
+
}
|
|
72
|
+
// TODO Float with decimal_places
|
|
73
|
+
if (valHasChanged) {
|
|
74
|
+
changes[name] = appRow[name];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return changes;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const allowUpdate = (table, row, user) => {
|
|
82
|
+
const role = user?.role_id || 100;
|
|
83
|
+
return table.min_role_write >= role || table.is_owner(user, row);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
const allowInsert = (table, row, user) => {
|
|
87
|
+
const role = user?.role_id || 100;
|
|
88
|
+
return table.min_role_write >= role;
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const syncRows = async (table, dbRows, appRows, user, dbClient) => {
|
|
92
|
+
const dbRowsLookup = {};
|
|
93
|
+
for (const row of dbRows) {
|
|
94
|
+
dbRowsLookup[row.id] = row;
|
|
95
|
+
}
|
|
96
|
+
const translatedIds = [];
|
|
97
|
+
for (const appRow of appRows.map((row) => pickFields(table, row))) {
|
|
98
|
+
if (!appRow.id) continue;
|
|
99
|
+
const dbRow = dbRowsLookup[appRow.id];
|
|
100
|
+
if (dbRow) {
|
|
101
|
+
const changes = getChanges(table, dbRow, appRow);
|
|
102
|
+
if (Object.keys(changes).length > 0 && allowUpdate(table, dbRow, user)) {
|
|
103
|
+
await db.update(table.name, changes, dbRow.id, { client: dbClient });
|
|
104
|
+
}
|
|
105
|
+
} else if (allowInsert(table, appRow, user)) {
|
|
106
|
+
const idFromApp = appRow.id;
|
|
107
|
+
delete appRow.id;
|
|
108
|
+
const newId = await db.insert(table.name, appRow, { client: dbClient });
|
|
109
|
+
if (newId !== idFromApp)
|
|
110
|
+
translatedIds.push({ from: idFromApp, to: newId });
|
|
111
|
+
} else {
|
|
112
|
+
getState().log(
|
|
113
|
+
3,
|
|
114
|
+
`Skipping id: '${appRow.id}' from app of table '${table.name}'`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return translatedIds;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Sync the database to the state of an offline session with the mobile app
|
|
123
|
+
*/
|
|
124
|
+
router.post(
|
|
125
|
+
"/table_data",
|
|
126
|
+
error_catcher(async (req, res) => {
|
|
127
|
+
// TODO public user
|
|
128
|
+
// TODO sqlite
|
|
129
|
+
getState().log(
|
|
130
|
+
4,
|
|
131
|
+
`POST /sync/table_data user: '${req.user ? req.user.id : "public"}'`
|
|
132
|
+
);
|
|
133
|
+
const role = req.user ? req.user.role_id : 100;
|
|
134
|
+
const client = db.isSQLite ? db : await db.getClient();
|
|
135
|
+
const selectOpts = req.user ? { forUser: req.user } : { forPublic: true };
|
|
136
|
+
try {
|
|
137
|
+
await client.query("BEGIN");
|
|
138
|
+
await client.query("SET CONSTRAINTS ALL DEFERRED");
|
|
139
|
+
const translateIds = {};
|
|
140
|
+
for (const [tblName, appRows] of Object.entries(req.body.data) || []) {
|
|
141
|
+
if (tblName !== "users") {
|
|
142
|
+
const table = Table.findOne({ name: tblName });
|
|
143
|
+
if (table) {
|
|
144
|
+
const dbRows =
|
|
145
|
+
role <= table.min_role_write
|
|
146
|
+
? await table.getRows({}, selectOpts)
|
|
147
|
+
: (await table.getRows({}, selectOpts)).filter((row) =>
|
|
148
|
+
table.is_owner(req.user, row)
|
|
149
|
+
);
|
|
150
|
+
const translated = await syncRows(
|
|
151
|
+
table,
|
|
152
|
+
dbRows,
|
|
153
|
+
appRows,
|
|
154
|
+
req.user,
|
|
155
|
+
client
|
|
156
|
+
);
|
|
157
|
+
if (translated.length > 0) translateIds[tblName] = translated;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
await client.query("COMMIT");
|
|
162
|
+
if (!db.isSQLite) await client.release(true);
|
|
163
|
+
res.json({ translateIds });
|
|
164
|
+
} catch (error) {
|
|
165
|
+
await client.query("ROLLBACK");
|
|
166
|
+
getState().log(2, `POST /sync/table_data error: '${error.message}'`);
|
|
167
|
+
res.status(400).json({ error: error.message || error });
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
);
|
package/routes/tables.js
CHANGED
|
@@ -53,6 +53,8 @@ const { getState } = require("@saltcorn/data/db/state");
|
|
|
53
53
|
const { cardHeaderTabs } = require("@saltcorn/markup/layout_utils");
|
|
54
54
|
const { tablesList } = require("./common_lists");
|
|
55
55
|
const { InvalidConfiguration } = require("@saltcorn/data/utils");
|
|
56
|
+
const { sleep } = require("@saltcorn/data/utils");
|
|
57
|
+
|
|
56
58
|
const path = require("path");
|
|
57
59
|
/**
|
|
58
60
|
* @type {object}
|
|
@@ -913,10 +915,12 @@ router.post(
|
|
|
913
915
|
rest.provider_name !== "Database table"
|
|
914
916
|
) {
|
|
915
917
|
const table = await Table.create(name, rest);
|
|
918
|
+
await sleep(500); // Allow other workers to load this view
|
|
916
919
|
res.redirect(`/table/provider-cfg/${table.id}`);
|
|
917
920
|
} else {
|
|
918
921
|
delete rest.provider_name;
|
|
919
922
|
const table = await Table.create(name, rest);
|
|
923
|
+
await sleep(500); // Allow other workers to load this view
|
|
920
924
|
req.flash("success", req.__(`Table %s created`, name));
|
|
921
925
|
res.redirect(`/table/${table.id}`);
|
|
922
926
|
}
|
package/routes/tenant.js
CHANGED
|
@@ -42,7 +42,7 @@ const {
|
|
|
42
42
|
code,
|
|
43
43
|
} = require("@saltcorn/markup/tags");
|
|
44
44
|
const db = require("@saltcorn/data/db");
|
|
45
|
-
|
|
45
|
+
|
|
46
46
|
const { loadAllPlugins, loadAndSaveNewPlugin } = require("../load_plugins");
|
|
47
47
|
const { isAdmin, error_catcher } = require("./utils.js");
|
|
48
48
|
const User = require("@saltcorn/data/models/user");
|
|
@@ -53,6 +53,7 @@ const {
|
|
|
53
53
|
save_config_from_form,
|
|
54
54
|
} = require("../markup/admin.js");
|
|
55
55
|
const { getConfig } = require("@saltcorn/data/models/config");
|
|
56
|
+
//const {quote} = require("@saltcorn/db-common");
|
|
56
57
|
// todo add button backup / restore for particular tenant (available in admin tenants screens)
|
|
57
58
|
//const {
|
|
58
59
|
// create_backup,
|
|
@@ -75,6 +76,7 @@ const remove_leading_chars = (cs, s) =>
|
|
|
75
76
|
/**
|
|
76
77
|
* Declare Form to create Tenant
|
|
77
78
|
* @param {object} req - Request
|
|
79
|
+
* @param base_url - Base URL
|
|
78
80
|
* @returns {Form} - Saltcorn Form Declaration
|
|
79
81
|
* @category server
|
|
80
82
|
*/
|
|
@@ -109,8 +111,8 @@ const tenant_form = (req, base_url) =>
|
|
|
109
111
|
// TBD To allow few roles to create tenants - currently only one role has such rights simultaneously
|
|
110
112
|
const create_tenant_allowed = (req) => {
|
|
111
113
|
const required_role =
|
|
112
|
-
+getRootState().getConfig("role_to_create_tenant") ||
|
|
113
|
-
const user_role = req.user ? req.user.role_id :
|
|
114
|
+
+getRootState().getConfig("role_to_create_tenant") || 100;
|
|
115
|
+
const user_role = req.user ? req.user.role_id : 100;
|
|
114
116
|
return user_role <= required_role;
|
|
115
117
|
};
|
|
116
118
|
|
|
@@ -227,6 +229,7 @@ router.get(
|
|
|
227
229
|
* Return URL of new Tenant
|
|
228
230
|
* @param {object} req - Request
|
|
229
231
|
* @param {string} subdomain - Tenant Subdomain name string
|
|
232
|
+
* @param base_url - Base URL
|
|
230
233
|
* @returns {string}
|
|
231
234
|
*/
|
|
232
235
|
const getNewURL = (req, subdomain, base_url) => {
|
|
@@ -280,7 +283,7 @@ router.post(
|
|
|
280
283
|
const description = valres.success.description;
|
|
281
284
|
// get list of tenants
|
|
282
285
|
const allTens = await getAllTenants();
|
|
283
|
-
if (allTens.includes(subdomain) || !subdomain) {
|
|
286
|
+
if (allTens.includes(subdomain) || !subdomain || subdomain === "public") {
|
|
284
287
|
form.errors.subdomain = req.__(
|
|
285
288
|
"A site with this subdomain already exists"
|
|
286
289
|
);
|
|
@@ -446,7 +449,7 @@ const tenant_settings_form = (req) =>
|
|
|
446
449
|
"tenant_template",
|
|
447
450
|
"tenant_baseurl",
|
|
448
451
|
"tenant_create_unauth_redirect",
|
|
449
|
-
{ section_header: "Tenant application capabilities" },
|
|
452
|
+
{ section_header: req.__("Tenant application capabilities") },
|
|
450
453
|
"tenants_install_git",
|
|
451
454
|
"tenants_set_npm_modules",
|
|
452
455
|
"tenants_unsafe_plugins",
|
|
@@ -536,8 +539,11 @@ const get_tenant_info = async (subdomain) => {
|
|
|
536
539
|
// get tenant row
|
|
537
540
|
const ten = await Tenant.findOne({ subdomain: saneDomain });
|
|
538
541
|
if (ten) {
|
|
542
|
+
//info.ten = ten;
|
|
539
543
|
info.description = ten.description;
|
|
540
544
|
info.created = ten.created;
|
|
545
|
+
info.template = ten.template;
|
|
546
|
+
info.email = ten.email;
|
|
541
547
|
}
|
|
542
548
|
|
|
543
549
|
// get data from tenant schema
|
|
@@ -547,10 +553,19 @@ const get_tenant_info = async (subdomain) => {
|
|
|
547
553
|
if (firstUser && firstUser.length > 0) {
|
|
548
554
|
info.first_user_email = firstUser[0].email;
|
|
549
555
|
}
|
|
556
|
+
// todo sort in alphabet order
|
|
557
|
+
// config items count
|
|
558
|
+
info.nconfigs = await db.count("_sc_config");
|
|
559
|
+
// error messages count
|
|
560
|
+
info.nerrors = await db.count("_sc_errors");
|
|
561
|
+
// event log
|
|
562
|
+
info.nevent_log = await db.count("_sc_event_log");
|
|
550
563
|
// users count
|
|
551
564
|
info.nusers = await db.count("users");
|
|
552
565
|
// roles count
|
|
553
566
|
info.nroles = await db.count("_sc_roles");
|
|
567
|
+
// table_constraints count
|
|
568
|
+
info.ntable_constraints = await db.count("_sc_table_constraints");
|
|
554
569
|
// tables count
|
|
555
570
|
info.ntables = await db.count("_sc_tables");
|
|
556
571
|
// table fields count
|
|
@@ -561,19 +576,25 @@ const get_tenant_info = async (subdomain) => {
|
|
|
561
576
|
info.nfiles = await db.count("_sc_files");
|
|
562
577
|
// pages count
|
|
563
578
|
info.npages = await db.count("_sc_pages");
|
|
564
|
-
// triggers (actions)
|
|
579
|
+
// triggers (actions) count
|
|
565
580
|
info.nactions = await db.count("_sc_triggers");
|
|
566
|
-
// error messages count
|
|
567
|
-
info.nerrors = await db.count("_sc_errors");
|
|
568
|
-
// config items count
|
|
569
|
-
info.nconfigs = await db.count("_sc_config");
|
|
570
581
|
// plugins count
|
|
571
582
|
info.nplugins = await db.count("_sc_plugins");
|
|
572
583
|
// migration count
|
|
573
584
|
info.nmigrations = await db.count("_sc_migrations");
|
|
574
585
|
// library count
|
|
575
586
|
info.nlibrary = await db.count("_sc_library");
|
|
576
|
-
//
|
|
587
|
+
// notifications
|
|
588
|
+
info.nnotifications = await db.count("_sc_notifications");
|
|
589
|
+
// tags
|
|
590
|
+
info.ntags = await db.count("_sc_tags");
|
|
591
|
+
// tag_entries
|
|
592
|
+
info.ntag_entries = await db.count("_sc_tag_entries");
|
|
593
|
+
// snapshots
|
|
594
|
+
info.nsnapshots = await db.count("_sc_snapshots");
|
|
595
|
+
// session - Only for main app?
|
|
596
|
+
//info.nsession = await db.count("_sc_session");
|
|
597
|
+
|
|
577
598
|
// base url
|
|
578
599
|
info.base_url = await getConfig("base_url");
|
|
579
600
|
return info;
|
|
@@ -628,51 +649,76 @@ router.get(
|
|
|
628
649
|
{ href: "mailto:" + info.first_user_email },
|
|
629
650
|
info.first_user_email
|
|
630
651
|
)
|
|
631
|
-
)
|
|
652
|
+
),
|
|
653
|
+
th(req.__("Template")),
|
|
654
|
+
td(a({ href: info.base_url }, info.template))
|
|
632
655
|
),
|
|
633
656
|
tr(
|
|
634
657
|
th(req.__("Users")),
|
|
635
|
-
td(a({ href: info.base_url + "useradmin" }, info.nusers))
|
|
636
|
-
),
|
|
637
|
-
tr(
|
|
658
|
+
td(a({ href: info.base_url + "useradmin" }, info.nusers)),
|
|
638
659
|
th(req.__("Roles")),
|
|
639
660
|
td(a({ href: info.base_url + "roleadmin" }, info.nroles))
|
|
640
661
|
),
|
|
641
662
|
tr(
|
|
642
663
|
th(req.__("Tables")),
|
|
643
|
-
td(a({ href: info.base_url + "table" }, info.ntables))
|
|
644
|
-
),
|
|
645
|
-
tr(
|
|
664
|
+
td(a({ href: info.base_url + "table" }, info.ntables)),
|
|
646
665
|
th(req.__("Table columns")),
|
|
647
666
|
td(a({ href: info.base_url + "table" }, info.nfields))
|
|
648
667
|
),
|
|
649
668
|
tr(
|
|
650
|
-
th(req.__("
|
|
651
|
-
td(
|
|
669
|
+
th(req.__("Table constraints")),
|
|
670
|
+
td(
|
|
671
|
+
a(
|
|
672
|
+
{ href: info.base_url + "table" },
|
|
673
|
+
info.ntable_constraints
|
|
674
|
+
)
|
|
675
|
+
),
|
|
676
|
+
th(req.__("Library")),
|
|
677
|
+
td(a({ href: info.base_url + "library/list" }, info.nlibrary))
|
|
652
678
|
),
|
|
653
679
|
tr(
|
|
680
|
+
th(req.__("Views")),
|
|
681
|
+
td(a({ href: info.base_url + "viewedit" }, info.nviews)),
|
|
654
682
|
th(req.__("Pages")),
|
|
655
683
|
td(a({ href: info.base_url + "pageedit" }, info.npages))
|
|
656
684
|
),
|
|
657
685
|
tr(
|
|
658
686
|
th(req.__("Files")),
|
|
659
|
-
td(a({ href: info.base_url + "files" }, info.nfiles))
|
|
660
|
-
),
|
|
661
|
-
tr(
|
|
687
|
+
td(a({ href: info.base_url + "files" }, info.nfiles)),
|
|
662
688
|
th(req.__("Actions")),
|
|
663
689
|
td(a({ href: info.base_url + "actions" }, info.nactions))
|
|
664
690
|
),
|
|
665
691
|
tr(
|
|
666
692
|
th(req.__("Modules")),
|
|
667
|
-
td(a({ href: info.base_url + "plugins" }, info.nplugins))
|
|
668
|
-
),
|
|
669
|
-
tr(
|
|
693
|
+
td(a({ href: info.base_url + "plugins" }, info.nplugins)),
|
|
670
694
|
th(req.__("Configuration items")),
|
|
671
695
|
td(a({ href: info.base_url + "admin" }, info.nconfigs))
|
|
672
696
|
),
|
|
673
697
|
tr(
|
|
698
|
+
// Crashlogs only for main site?
|
|
674
699
|
th(req.__("Crashlogs")),
|
|
675
|
-
td(a({ href: info.base_url + "crashlog" }, info.nerrors))
|
|
700
|
+
td(a({ href: info.base_url + "crashlog" }, info.nerrors)),
|
|
701
|
+
//th(req.__("Sessions")),
|
|
702
|
+
//td(a({ href: info.base_url + "crashlog" }, info.nsessions)),
|
|
703
|
+
th(req.__("Event logs")),
|
|
704
|
+
td(a({ href: info.base_url + "eventlog" }, info.nevent_log))
|
|
705
|
+
// Notifications only for main site?
|
|
706
|
+
//th(req.__("Notifications")),
|
|
707
|
+
//td(a({ href: info.base_url + "???" }, info.nnotifications)),
|
|
708
|
+
),
|
|
709
|
+
tr(
|
|
710
|
+
th(req.__("Snapshots")),
|
|
711
|
+
td(
|
|
712
|
+
a({ href: info.base_url + "admin/backup" }, info.nsnapshots)
|
|
713
|
+
),
|
|
714
|
+
th(req.__("Migrations")),
|
|
715
|
+
td(a({ href: info.base_url + "admin" }, info.nmigrations))
|
|
716
|
+
),
|
|
717
|
+
tr(
|
|
718
|
+
th(req.__("Tags")),
|
|
719
|
+
td(a({ href: info.base_url + "tag" }, info.ntags)),
|
|
720
|
+
th(req.__("Tag Entries")),
|
|
721
|
+
td(a({ href: info.base_url + "tag" }, info.ntag_entries))
|
|
676
722
|
)
|
|
677
723
|
),
|
|
678
724
|
],
|
|
@@ -697,6 +743,7 @@ router.get(
|
|
|
697
743
|
name: "description",
|
|
698
744
|
label: req.__("Description"),
|
|
699
745
|
type: "String",
|
|
746
|
+
fieldview: "textarea",
|
|
700
747
|
},
|
|
701
748
|
],
|
|
702
749
|
values: {
|
package/routes/view.js
CHANGED
|
@@ -42,7 +42,7 @@ router.get(
|
|
|
42
42
|
const { viewname } = req.params;
|
|
43
43
|
const query = { ...req.query };
|
|
44
44
|
const view = await View.findOne({ name: viewname });
|
|
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 state = getState();
|
|
47
47
|
state.log(3, `Route /view/${viewname} user=${req.user?.id}`);
|
|
48
48
|
if (!view) {
|
|
@@ -157,7 +157,7 @@ router.post(
|
|
|
157
157
|
"/:viewname/:route",
|
|
158
158
|
error_catcher(async (req, res) => {
|
|
159
159
|
const { viewname, route } = req.params;
|
|
160
|
-
const role = req.user && req.user.id ? req.user.role_id :
|
|
160
|
+
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
161
161
|
const state = getState();
|
|
162
162
|
state.log(
|
|
163
163
|
3,
|
|
@@ -191,7 +191,7 @@ router.post(
|
|
|
191
191
|
setTenant,
|
|
192
192
|
error_catcher(async (req, res) => {
|
|
193
193
|
const { viewname } = req.params;
|
|
194
|
-
const role = req.user && req.user.id ? req.user.role_id :
|
|
194
|
+
const role = req.user && req.user.id ? req.user.role_id : 100;
|
|
195
195
|
const query = { ...req.query };
|
|
196
196
|
const state = getState();
|
|
197
197
|
state.log(3, `Route /view/${viewname} POST user=${req.user?.id}`);
|
package/routes/viewedit.js
CHANGED
|
@@ -8,16 +8,9 @@
|
|
|
8
8
|
const Router = require("express-promise-router");
|
|
9
9
|
|
|
10
10
|
const { renderForm, renderBuilder, alert } = require("@saltcorn/markup");
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
div,
|
|
15
|
-
script,
|
|
16
|
-
text,
|
|
17
|
-
domReady,
|
|
18
|
-
code,
|
|
19
|
-
pre,
|
|
20
|
-
} = require("@saltcorn/markup/tags");
|
|
11
|
+
const tags = require("@saltcorn/markup/tags");
|
|
12
|
+
const { p, a, div, script, text, domReady, code, pre, tbody, tr, th, td } =
|
|
13
|
+
tags;
|
|
21
14
|
|
|
22
15
|
const { getState } = require("@saltcorn/data/db/state");
|
|
23
16
|
const { isAdmin, error_catcher, addOnDoneRedirect } = require("./utils.js");
|
|
@@ -30,6 +23,7 @@ const Workflow = require("@saltcorn/data/models/workflow");
|
|
|
30
23
|
const User = require("@saltcorn/data/models/user");
|
|
31
24
|
const Page = require("@saltcorn/data/models/page");
|
|
32
25
|
const db = require("@saltcorn/data/db");
|
|
26
|
+
const { sleep } = require("@saltcorn/data/utils");
|
|
33
27
|
|
|
34
28
|
const { add_to_menu } = require("@saltcorn/admin-models/models/pack");
|
|
35
29
|
|
|
@@ -305,6 +299,7 @@ router.get(
|
|
|
305
299
|
const roles = await User.get_roles();
|
|
306
300
|
const pages = await Page.find();
|
|
307
301
|
const form = await viewForm(req, tableOptions, roles, pages, viewrow);
|
|
302
|
+
const inbound_connected = await viewrow.inbound_connected_objects();
|
|
308
303
|
form.hidden("id");
|
|
309
304
|
res.sendWrap(req.__(`Edit view`), {
|
|
310
305
|
above: [
|
|
@@ -328,7 +323,6 @@ router.get(
|
|
|
328
323
|
},
|
|
329
324
|
{
|
|
330
325
|
type: "card",
|
|
331
|
-
|
|
332
326
|
title: req.__("View configuration"),
|
|
333
327
|
contents: {
|
|
334
328
|
type: "tabs",
|
|
@@ -340,6 +334,24 @@ router.get(
|
|
|
340
334
|
titles: [req.__("Show configuration object")],
|
|
341
335
|
},
|
|
342
336
|
},
|
|
337
|
+
{
|
|
338
|
+
type: "card",
|
|
339
|
+
title: req.__("Connected views"),
|
|
340
|
+
contents: tags.table(
|
|
341
|
+
tbody(
|
|
342
|
+
tr(
|
|
343
|
+
th({ class: "me-2" }, req.__("Embedded in")),
|
|
344
|
+
td(
|
|
345
|
+
inbound_connected.embeddedViews.map((v) => v.name).join(", ")
|
|
346
|
+
)
|
|
347
|
+
),
|
|
348
|
+
tr(
|
|
349
|
+
th({ class: "me-2" }, req.__("Linked from")),
|
|
350
|
+
td(inbound_connected.linkedViews.map((v) => v.name).join(", "))
|
|
351
|
+
)
|
|
352
|
+
)
|
|
353
|
+
),
|
|
354
|
+
},
|
|
343
355
|
],
|
|
344
356
|
});
|
|
345
357
|
})
|
|
@@ -458,6 +470,7 @@ router.post(
|
|
|
458
470
|
else v.configuration = {};
|
|
459
471
|
//console.log(v);
|
|
460
472
|
await View.create(v);
|
|
473
|
+
await sleep(500); // Allow other workers to load this view
|
|
461
474
|
}
|
|
462
475
|
res.redirect(
|
|
463
476
|
addOnDoneRedirect(
|
package/tests/admin.test.js
CHANGED
|
@@ -29,7 +29,7 @@ beforeAll(async () => {
|
|
|
29
29
|
await File.from_req_files(
|
|
30
30
|
{ mimetype: "image/png", name: "rick.png", mv, size: 245752 },
|
|
31
31
|
1,
|
|
32
|
-
|
|
32
|
+
40
|
|
33
33
|
);
|
|
34
34
|
});
|
|
35
35
|
|
|
@@ -272,7 +272,7 @@ describe("menu editor", () => {
|
|
|
272
272
|
url: "",
|
|
273
273
|
type: "View",
|
|
274
274
|
label: "BarMenu",
|
|
275
|
-
min_role: "
|
|
275
|
+
min_role: "100",
|
|
276
276
|
pagename: null,
|
|
277
277
|
viewname: "dqwdw",
|
|
278
278
|
},
|
|
@@ -321,32 +321,32 @@ describe("roleadmin", () => {
|
|
|
321
321
|
await request(app)
|
|
322
322
|
.post("/roleadmin/edit")
|
|
323
323
|
.set("Cookie", loginCookie)
|
|
324
|
-
.send("id=
|
|
324
|
+
.send("id=50")
|
|
325
325
|
.send("role=muppets")
|
|
326
326
|
.expect(toRedirect("/roleadmin"));
|
|
327
327
|
const roles = await User.get_roles();
|
|
328
|
-
expect(roles).toContainEqual({ id:
|
|
328
|
+
expect(roles).toContainEqual({ id: 50, role: "muppets" });
|
|
329
329
|
});
|
|
330
330
|
it("show set layout for role", async () => {
|
|
331
331
|
const app = await getApp({ disableCsrf: true });
|
|
332
332
|
const loginCookie = await getAdminLoginCookie();
|
|
333
333
|
await request(app)
|
|
334
|
-
.post("/roleadmin/setrolelayout/
|
|
334
|
+
.post("/roleadmin/setrolelayout/50")
|
|
335
335
|
.set("Cookie", loginCookie)
|
|
336
336
|
.send("layout=tabler")
|
|
337
337
|
.expect(toRedirect("/roleadmin"));
|
|
338
338
|
const roles = await User.get_roles();
|
|
339
|
-
expect(roles).toContainEqual({ id:
|
|
339
|
+
expect(roles).toContainEqual({ id: 50, role: "muppets" });
|
|
340
340
|
});
|
|
341
341
|
it("show delete role", async () => {
|
|
342
342
|
const app = await getApp({ disableCsrf: true });
|
|
343
343
|
const loginCookie = await getAdminLoginCookie();
|
|
344
344
|
await request(app)
|
|
345
|
-
.post("/roleadmin/delete/
|
|
345
|
+
.post("/roleadmin/delete/50")
|
|
346
346
|
.set("Cookie", loginCookie)
|
|
347
347
|
.expect(toRedirect("/roleadmin"));
|
|
348
348
|
const roles = await User.get_roles();
|
|
349
|
-
expect(roles).not.toContainEqual({ id:
|
|
349
|
+
expect(roles).not.toContainEqual({ id: 50, role: "muppets" });
|
|
350
350
|
});
|
|
351
351
|
});
|
|
352
352
|
/**
|
package/tests/auth.test.js
CHANGED
|
@@ -205,7 +205,7 @@ describe("user admin", () => {
|
|
|
205
205
|
.post("/useradmin/save")
|
|
206
206
|
.send("email=staff2@foo.com")
|
|
207
207
|
.send("password=fideRGE54lio")
|
|
208
|
-
.send("role_id=
|
|
208
|
+
.send("role_id=80")
|
|
209
209
|
.set("Cookie", loginCookie)
|
|
210
210
|
.expect(toRedirect("/useradmin"));
|
|
211
211
|
});
|
|
@@ -223,7 +223,7 @@ describe("user admin", () => {
|
|
|
223
223
|
const app = await getApp({ disableCsrf: true });
|
|
224
224
|
const loginCookie = await getAdminLoginCookie();
|
|
225
225
|
const user = await User.findOne({ email: "staff2@foo.com" });
|
|
226
|
-
expect(user.role_id).toBe(
|
|
226
|
+
expect(user.role_id).toBe(80);
|
|
227
227
|
await request(app)
|
|
228
228
|
.get(`/useradmin/${user.id}`)
|
|
229
229
|
.set("Cookie", loginCookie)
|
|
@@ -238,11 +238,11 @@ describe("user admin", () => {
|
|
|
238
238
|
.post("/useradmin/save")
|
|
239
239
|
.send("email=staff2@foo.com")
|
|
240
240
|
.send(`id=${user.id}`)
|
|
241
|
-
.send("role_id=
|
|
241
|
+
.send("role_id=40")
|
|
242
242
|
.set("Cookie", loginCookie)
|
|
243
243
|
.expect(toRedirect("/useradmin"));
|
|
244
244
|
const edituser = await User.findOne({ email: "staff2@foo.com" });
|
|
245
|
-
expect(edituser.role_id).toBe(
|
|
245
|
+
expect(edituser.role_id).toBe(40);
|
|
246
246
|
});
|
|
247
247
|
it("tries to create new user with existing email", async () => {
|
|
248
248
|
const app = await getApp({ disableCsrf: true });
|
|
@@ -251,7 +251,7 @@ describe("user admin", () => {
|
|
|
251
251
|
.post("/useradmin/save")
|
|
252
252
|
.send("email=staff2@foo.com")
|
|
253
253
|
.send("password=fideRGE54lio")
|
|
254
|
-
.send("role_id=
|
|
254
|
+
.send("role_id=80")
|
|
255
255
|
.set("Cookie", loginCookie)
|
|
256
256
|
.expect(toRedirect("/useradmin"));
|
|
257
257
|
const editusers = await User.find({ email: "staff2@foo.com" });
|
|
@@ -342,7 +342,7 @@ describe("User fields", () => {
|
|
|
342
342
|
configuration: {
|
|
343
343
|
columns: [
|
|
344
344
|
{ type: "Field", fieldview: "edit", field_name: "height" },
|
|
345
|
-
{ type: "Action", minRole:
|
|
345
|
+
{ type: "Action", minRole: 100, action_name: "Save" },
|
|
346
346
|
],
|
|
347
347
|
layout: {
|
|
348
348
|
above: [
|
|
@@ -368,7 +368,7 @@ describe("User fields", () => {
|
|
|
368
368
|
],
|
|
369
369
|
},
|
|
370
370
|
{ type: "line_break" },
|
|
371
|
-
{ type: "action", minRole:
|
|
371
|
+
{ type: "action", minRole: 100, action_name: "Save" },
|
|
372
372
|
],
|
|
373
373
|
},
|
|
374
374
|
},
|
|
@@ -466,7 +466,7 @@ describe("signup with custom login form", () => {
|
|
|
466
466
|
{
|
|
467
467
|
type: "action",
|
|
468
468
|
rndid: "63f01b",
|
|
469
|
-
minRole:
|
|
469
|
+
minRole: 100,
|
|
470
470
|
isFormula: {},
|
|
471
471
|
action_name: "Login",
|
|
472
472
|
action_label: "Login",
|
|
@@ -476,7 +476,7 @@ describe("signup with custom login form", () => {
|
|
|
476
476
|
{
|
|
477
477
|
type: "action",
|
|
478
478
|
rndid: "45dd57",
|
|
479
|
-
minRole:
|
|
479
|
+
minRole: 100,
|
|
480
480
|
isFormula: {},
|
|
481
481
|
action_name: "Login with github",
|
|
482
482
|
configuration: {},
|
|
@@ -489,7 +489,7 @@ describe("signup with custom login form", () => {
|
|
|
489
489
|
{
|
|
490
490
|
type: "Action",
|
|
491
491
|
rndid: "63f01b",
|
|
492
|
-
minRole:
|
|
492
|
+
minRole: 100,
|
|
493
493
|
isFormula: {},
|
|
494
494
|
action_name: "Login",
|
|
495
495
|
action_label: "Login",
|
|
@@ -499,7 +499,7 @@ describe("signup with custom login form", () => {
|
|
|
499
499
|
{
|
|
500
500
|
type: "Action",
|
|
501
501
|
rndid: "45dd57",
|
|
502
|
-
minRole:
|
|
502
|
+
minRole: 100,
|
|
503
503
|
isFormula: {},
|
|
504
504
|
action_name: "Login with github",
|
|
505
505
|
configuration: {},
|
|
@@ -573,7 +573,7 @@ describe("signup with custom login form", () => {
|
|
|
573
573
|
{
|
|
574
574
|
type: "action",
|
|
575
575
|
rndid: "63f01b",
|
|
576
|
-
minRole:
|
|
576
|
+
minRole: 100,
|
|
577
577
|
isFormula: {},
|
|
578
578
|
action_name: "Sign up",
|
|
579
579
|
action_style: "btn-primary",
|
|
@@ -588,7 +588,7 @@ describe("signup with custom login form", () => {
|
|
|
588
588
|
{
|
|
589
589
|
type: "Action",
|
|
590
590
|
rndid: "63f01b",
|
|
591
|
-
minRole:
|
|
591
|
+
minRole: 100,
|
|
592
592
|
isFormula: {},
|
|
593
593
|
action_name: "Sign up",
|
|
594
594
|
action_style: "btn-primary",
|