@saltcorn/server 0.7.4 → 0.8.0-beta.1
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 +18 -11
- package/auth/admin.js +370 -120
- package/auth/roleadmin.js +5 -23
- package/auth/routes.js +40 -15
- package/locales/de.json +1049 -273
- package/locales/en.json +58 -3
- package/locales/es.json +134 -134
- package/locales/it.json +6 -1
- package/locales/ru.json +44 -7
- package/markup/admin.js +46 -42
- package/markup/forms.js +4 -3
- package/package.json +8 -7
- package/public/blockly.js +19 -31
- package/public/diagram_utils.js +530 -0
- package/public/gridedit.js +4 -1
- package/public/jquery-menu-editor.min.js +112 -112
- package/public/saltcorn-common.js +31 -8
- package/public/saltcorn.css +11 -0
- package/public/saltcorn.js +211 -70
- package/restart_watcher.js +1 -0
- package/routes/actions.js +6 -14
- package/routes/admin.js +229 -79
- package/routes/api.js +19 -2
- package/routes/common_lists.js +137 -134
- package/routes/delete.js +6 -5
- package/routes/diagram.js +43 -117
- package/routes/edit.js +5 -10
- package/routes/fields.js +63 -29
- package/routes/files.js +137 -101
- package/routes/homepage.js +2 -2
- package/routes/infoarch.js +2 -2
- package/routes/list.js +12 -13
- package/routes/page.js +16 -3
- package/routes/pageedit.js +13 -8
- package/routes/scapi.js +1 -1
- package/routes/search.js +1 -1
- package/routes/tables.js +9 -14
- package/routes/tag_entries.js +31 -10
- package/routes/tags.js +10 -10
- package/routes/tenant.js +114 -50
- package/routes/utils.js +12 -0
- package/routes/view.js +3 -4
- package/routes/viewedit.js +57 -55
- package/serve.js +5 -0
- package/tests/admin.test.js +6 -2
- package/tests/auth.test.js +20 -0
- package/tests/fields.test.js +1 -0
- package/tests/files.test.js +11 -20
- package/tests/tenant.test.js +12 -2
- package/tests/viewedit.test.js +15 -1
package/routes/admin.js
CHANGED
|
@@ -97,13 +97,6 @@ const { getConfigFile } = require("@saltcorn/data/db/connect");
|
|
|
97
97
|
const os = require("os");
|
|
98
98
|
const Page = require("@saltcorn/data/models/page");
|
|
99
99
|
|
|
100
|
-
/**
|
|
101
|
-
* @type {object}
|
|
102
|
-
* @const
|
|
103
|
-
* @namespace routes/adminRouter
|
|
104
|
-
* @category server
|
|
105
|
-
* @subcategory routes
|
|
106
|
-
*/
|
|
107
100
|
const router = new Router();
|
|
108
101
|
module.exports = router;
|
|
109
102
|
|
|
@@ -123,9 +116,6 @@ const site_id_form = (req) =>
|
|
|
123
116
|
"base_url",
|
|
124
117
|
"page_custom_css",
|
|
125
118
|
"page_custom_html",
|
|
126
|
-
"development_mode",
|
|
127
|
-
"log_sql",
|
|
128
|
-
"log_level",
|
|
129
119
|
"plugins_store_endpoint",
|
|
130
120
|
"packs_store_endpoint",
|
|
131
121
|
...(getConfigFile() ? ["multitenancy_enabled"] : []),
|
|
@@ -139,7 +129,7 @@ const site_id_form = (req) =>
|
|
|
139
129
|
* @returns {Promise<Form>} form
|
|
140
130
|
*/
|
|
141
131
|
const email_form = async (req) => {
|
|
142
|
-
|
|
132
|
+
return await config_fields_form({
|
|
143
133
|
req,
|
|
144
134
|
field_names: [
|
|
145
135
|
"smtp_host",
|
|
@@ -151,10 +141,9 @@ const email_form = async (req) => {
|
|
|
151
141
|
],
|
|
152
142
|
action: "/admin/email",
|
|
153
143
|
});
|
|
154
|
-
return form;
|
|
155
144
|
};
|
|
156
145
|
|
|
157
|
-
const app_files_table = (files, req) =>
|
|
146
|
+
const app_files_table = (files, buildDirName, req) =>
|
|
158
147
|
mkTable(
|
|
159
148
|
[
|
|
160
149
|
{
|
|
@@ -163,9 +152,21 @@ const app_files_table = (files, req) =>
|
|
|
163
152
|
},
|
|
164
153
|
{ label: req.__("Size (KiB)"), key: "size_kb", align: "right" },
|
|
165
154
|
{ label: req.__("Media type"), key: (r) => r.mimetype },
|
|
155
|
+
{
|
|
156
|
+
label: req.__("Open"),
|
|
157
|
+
key: (r) =>
|
|
158
|
+
link(
|
|
159
|
+
`/files/serve/mobile_app/${buildDirName}/${r.filename}`,
|
|
160
|
+
req.__("Open")
|
|
161
|
+
),
|
|
162
|
+
},
|
|
166
163
|
{
|
|
167
164
|
label: req.__("Download"),
|
|
168
|
-
key: (r) =>
|
|
165
|
+
key: (r) =>
|
|
166
|
+
link(
|
|
167
|
+
`/files/download/mobile_app/${buildDirName}/${r.filename}`,
|
|
168
|
+
req.__("Download")
|
|
169
|
+
),
|
|
169
170
|
},
|
|
170
171
|
],
|
|
171
172
|
files
|
|
@@ -734,6 +735,7 @@ router.post(
|
|
|
734
735
|
await auto_backup_now();
|
|
735
736
|
req.flash("success", req.__("Backup successful"));
|
|
736
737
|
} catch (e) {
|
|
738
|
+
getState().log(1, e);
|
|
737
739
|
req.flash("error", e.message);
|
|
738
740
|
}
|
|
739
741
|
res.json({ reload_page: true });
|
|
@@ -873,12 +875,40 @@ router.get(
|
|
|
873
875
|
),
|
|
874
876
|
tr(th(req.__("Node.js version")), td(process.version)),
|
|
875
877
|
tr(
|
|
876
|
-
th(req.__("Database")),
|
|
878
|
+
th(req.__("Database type")),
|
|
877
879
|
td(db.isSQLite ? "SQLite " : "PostgreSQL ", dbversion)
|
|
878
880
|
),
|
|
881
|
+
(isRoot?
|
|
882
|
+
tr(
|
|
883
|
+
th(req.__("Database host")),
|
|
884
|
+
td(db.connectObj.host)
|
|
885
|
+
)
|
|
886
|
+
: ""),
|
|
887
|
+
(isRoot?
|
|
888
|
+
tr(
|
|
889
|
+
th(req.__("Database port")),
|
|
890
|
+
td(db.connectObj.port)
|
|
891
|
+
)
|
|
892
|
+
: ""),
|
|
893
|
+
(isRoot?
|
|
894
|
+
tr(
|
|
895
|
+
th(req.__("Database name")),
|
|
896
|
+
td(db.connectObj.database)
|
|
897
|
+
)
|
|
898
|
+
: ""),
|
|
899
|
+
(isRoot?
|
|
900
|
+
tr(
|
|
901
|
+
th(req.__("Database user")),
|
|
902
|
+
td(db.connectObj.user)
|
|
903
|
+
)
|
|
904
|
+
: ""),
|
|
905
|
+
tr(
|
|
906
|
+
th(req.__("Database schema")),
|
|
907
|
+
td(db.getTenantSchema())
|
|
908
|
+
),
|
|
879
909
|
tr(
|
|
880
|
-
|
|
881
|
-
|
|
910
|
+
th(req.__("Process uptime")),
|
|
911
|
+
td(moment(get_process_init_time()).fromNow(true))
|
|
882
912
|
)
|
|
883
913
|
)
|
|
884
914
|
),
|
|
@@ -1275,10 +1305,11 @@ const buildDialogScript = () => {
|
|
|
1275
1305
|
|
|
1276
1306
|
function handleMessages() {
|
|
1277
1307
|
notifyAlert("This is still under development and might run longer.")
|
|
1278
|
-
${
|
|
1308
|
+
${
|
|
1309
|
+
getState().getConfig("apple_team_id") &&
|
|
1279
1310
|
getState().getConfig("apple_team_id") !== "null"
|
|
1280
|
-
|
|
1281
|
-
|
|
1311
|
+
? ""
|
|
1312
|
+
: `
|
|
1282
1313
|
if ($("#iOSCheckboxId")[0].checked) {
|
|
1283
1314
|
notifyAlert(
|
|
1284
1315
|
"No 'Apple Team ID' is configured, I will try to build a project for the iOS simulator."
|
|
@@ -1288,7 +1319,9 @@ const buildDialogScript = () => {
|
|
|
1288
1319
|
}
|
|
1289
1320
|
</script>`;
|
|
1290
1321
|
};
|
|
1291
|
-
|
|
1322
|
+
/**
|
|
1323
|
+
* Build mobile app
|
|
1324
|
+
*/
|
|
1292
1325
|
router.get(
|
|
1293
1326
|
"/build-mobile-app",
|
|
1294
1327
|
isAdmin,
|
|
@@ -1481,8 +1514,9 @@ router.get(
|
|
|
1481
1514
|
),
|
|
1482
1515
|
button(
|
|
1483
1516
|
{
|
|
1484
|
-
|
|
1485
|
-
|
|
1517
|
+
id: "buildMobileAppBtnId",
|
|
1518
|
+
type: "button",
|
|
1519
|
+
onClick: `build_mobile_app(this);`,
|
|
1486
1520
|
class: "btn btn-warning",
|
|
1487
1521
|
},
|
|
1488
1522
|
i({ class: "fas fa-hammer pe-2" }),
|
|
@@ -1498,6 +1532,57 @@ router.get(
|
|
|
1498
1532
|
})
|
|
1499
1533
|
);
|
|
1500
1534
|
|
|
1535
|
+
const checkFiles = async (outDir, fileNames) => {
|
|
1536
|
+
const rootFolder = await File.rootFolder();
|
|
1537
|
+
const mobile_app_dir = path.join(rootFolder.location, "mobile_app", outDir);
|
|
1538
|
+
const entries = fs.readdirSync(mobile_app_dir);
|
|
1539
|
+
return fileNames.some((fileName) => entries.indexOf(fileName) >= 0);
|
|
1540
|
+
};
|
|
1541
|
+
|
|
1542
|
+
// check if a build has finished (poll service)
|
|
1543
|
+
router.get(
|
|
1544
|
+
"/build-mobile-app/finished",
|
|
1545
|
+
isAdmin,
|
|
1546
|
+
error_catcher(async (req, res) => {
|
|
1547
|
+
const { build_dir } = req.query;
|
|
1548
|
+
res.json({
|
|
1549
|
+
finished: await checkFiles(build_dir, ["logs.txt", "error_logs.txt"]),
|
|
1550
|
+
});
|
|
1551
|
+
})
|
|
1552
|
+
);
|
|
1553
|
+
|
|
1554
|
+
router.get(
|
|
1555
|
+
"/build-mobile-app/result",
|
|
1556
|
+
isAdmin,
|
|
1557
|
+
error_catcher(async (req, res) => {
|
|
1558
|
+
const { build_dir_name } = req.query;
|
|
1559
|
+
const rootFolder = await File.rootFolder();
|
|
1560
|
+
const buildDir = path.join(
|
|
1561
|
+
rootFolder.location,
|
|
1562
|
+
"mobile_app",
|
|
1563
|
+
build_dir_name
|
|
1564
|
+
);
|
|
1565
|
+
const files = await Promise.all(
|
|
1566
|
+
fs
|
|
1567
|
+
.readdirSync(buildDir)
|
|
1568
|
+
.map(async (outFile) => await File.from_file_on_disk(outFile, buildDir))
|
|
1569
|
+
);
|
|
1570
|
+
const resultMsg = files.find((file) => file.filename === "logs.txt")
|
|
1571
|
+
? req.__("The build was successfully")
|
|
1572
|
+
: req.__("Unable to build the app");
|
|
1573
|
+
res.sendWrap(req.__(`Admin`), {
|
|
1574
|
+
above: [
|
|
1575
|
+
{
|
|
1576
|
+
type: "card",
|
|
1577
|
+
title: req.__("Build Result"),
|
|
1578
|
+
contents: div(resultMsg),
|
|
1579
|
+
},
|
|
1580
|
+
files.length > 0 ? app_files_table(files, build_dir_name, req) : "",
|
|
1581
|
+
],
|
|
1582
|
+
});
|
|
1583
|
+
})
|
|
1584
|
+
);
|
|
1585
|
+
|
|
1501
1586
|
router.post(
|
|
1502
1587
|
"/build-mobile-app",
|
|
1503
1588
|
isAdmin,
|
|
@@ -1512,24 +1597,27 @@ router.post(
|
|
|
1512
1597
|
serverURL,
|
|
1513
1598
|
} = req.body;
|
|
1514
1599
|
if (!androidPlatform && !iOSPlatform) {
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
);
|
|
1519
|
-
return res.redirect("/admin/build-mobile-app");
|
|
1600
|
+
return res.json({
|
|
1601
|
+
error: req.__("Please select at least one platform (android or iOS)."),
|
|
1602
|
+
});
|
|
1520
1603
|
}
|
|
1521
1604
|
if (!androidPlatform && useDocker) {
|
|
1522
|
-
|
|
1523
|
-
|
|
1605
|
+
return res.json({
|
|
1606
|
+
error: req.__("Only the android build supports docker."),
|
|
1607
|
+
});
|
|
1524
1608
|
}
|
|
1525
1609
|
if (!serverURL || serverURL.length == 0) {
|
|
1526
1610
|
serverURL = getState().getConfig("base_url") || "";
|
|
1527
1611
|
}
|
|
1528
1612
|
if (!serverURL.startsWith("http")) {
|
|
1529
|
-
|
|
1530
|
-
|
|
1613
|
+
return res.json({
|
|
1614
|
+
error: req.__("Please enter a valid server URL."),
|
|
1615
|
+
});
|
|
1531
1616
|
}
|
|
1532
|
-
const
|
|
1617
|
+
const outDirName = `build_${new Date().valueOf()}`;
|
|
1618
|
+
const rootFolder = await File.rootFolder();
|
|
1619
|
+
const buildDir = path.join(rootFolder.location, "mobile_app", outDirName);
|
|
1620
|
+
await File.new_folder(outDirName, "/mobile_app");
|
|
1533
1621
|
const spawnParams = [
|
|
1534
1622
|
"build-app",
|
|
1535
1623
|
"-e",
|
|
@@ -1537,7 +1625,7 @@ router.post(
|
|
|
1537
1625
|
"-t",
|
|
1538
1626
|
entryPointType,
|
|
1539
1627
|
"-c",
|
|
1540
|
-
|
|
1628
|
+
buildDir,
|
|
1541
1629
|
"-b",
|
|
1542
1630
|
`${os.userInfo().homedir}/mobile_app_build`,
|
|
1543
1631
|
];
|
|
@@ -1558,6 +1646,9 @@ router.post(
|
|
|
1558
1646
|
) {
|
|
1559
1647
|
spawnParams.push("--tenantAppName", db.getTenantSchema());
|
|
1560
1648
|
}
|
|
1649
|
+
// end http call, return the out directory name
|
|
1650
|
+
// the gui polls for results
|
|
1651
|
+
res.json({ build_dir_name: outDirName });
|
|
1561
1652
|
const child = spawn("saltcorn", spawnParams, {
|
|
1562
1653
|
stdio: ["ignore", "pipe", "pipe"],
|
|
1563
1654
|
cwd: ".",
|
|
@@ -1572,60 +1663,37 @@ router.post(
|
|
|
1572
1663
|
childOutputs.push(data.toString());
|
|
1573
1664
|
});
|
|
1574
1665
|
child.on("exit", async function (exitCode, signal) {
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
)
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
{
|
|
1587
|
-
type: "card",
|
|
1588
|
-
title: req.__("Build Result"),
|
|
1589
|
-
contents: div(req.__("The build was successfully")),
|
|
1590
|
-
},
|
|
1591
|
-
files.length > 0 ? app_files_table(files, req) : "",
|
|
1592
|
-
],
|
|
1593
|
-
});
|
|
1594
|
-
} else
|
|
1595
|
-
res.sendWrap(req.__(`Admin`), {
|
|
1596
|
-
above: [
|
|
1597
|
-
{
|
|
1598
|
-
type: "card",
|
|
1599
|
-
title: req.__("Build Result"),
|
|
1600
|
-
contents: div(
|
|
1601
|
-
req.__("Unable to build the app:"),
|
|
1602
|
-
pre(code(childOutputs.join("<br/>")))
|
|
1603
|
-
),
|
|
1604
|
-
},
|
|
1605
|
-
],
|
|
1606
|
-
});
|
|
1666
|
+
const logFile = exitCode === 0 ? "logs.txt" : "error_logs.txt";
|
|
1667
|
+
fs.writeFile(
|
|
1668
|
+
path.join(buildDir, logFile),
|
|
1669
|
+
childOutputs.join("\n"),
|
|
1670
|
+
(error) => {
|
|
1671
|
+
if (error) {
|
|
1672
|
+
console.log(`unable to write '${logFile}' to '${buildDir}'`);
|
|
1673
|
+
console.log(error);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
);
|
|
1607
1677
|
});
|
|
1608
1678
|
child.on("error", function (msg) {
|
|
1609
1679
|
const message = msg.message ? msg.message : msg.code;
|
|
1610
1680
|
const stack = msg.stack ? msg.stack : "";
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
},
|
|
1622
|
-
],
|
|
1623
|
-
});
|
|
1681
|
+
fs.writeFile(
|
|
1682
|
+
path.join(buildDir, "error_logs.txt"),
|
|
1683
|
+
[message, stack].join("\n"),
|
|
1684
|
+
(error) => {
|
|
1685
|
+
if (error) {
|
|
1686
|
+
console.log(`unable to write '${logFile}' to '${buildDir}'`);
|
|
1687
|
+
console.log(error);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
);
|
|
1624
1691
|
});
|
|
1625
1692
|
})
|
|
1626
1693
|
);
|
|
1627
1694
|
|
|
1628
1695
|
/**
|
|
1696
|
+
* Clear all
|
|
1629
1697
|
* @name post/clear-all
|
|
1630
1698
|
* @function
|
|
1631
1699
|
* @memberof module:routes/admin~routes/adminRouter
|
|
@@ -1744,3 +1812,85 @@ router.post(
|
|
|
1744
1812
|
}
|
|
1745
1813
|
})
|
|
1746
1814
|
);
|
|
1815
|
+
|
|
1816
|
+
/**
|
|
1817
|
+
* Developer settings form
|
|
1818
|
+
* @param {object} req request
|
|
1819
|
+
* @returns {Promise<Form>} form
|
|
1820
|
+
*/
|
|
1821
|
+
const dev_form = async (req) => {
|
|
1822
|
+
return await config_fields_form({
|
|
1823
|
+
req,
|
|
1824
|
+
field_names: [
|
|
1825
|
+
"development_mode",
|
|
1826
|
+
"log_sql",
|
|
1827
|
+
"log_level",
|
|
1828
|
+
],
|
|
1829
|
+
action: "/admin/dev",
|
|
1830
|
+
});
|
|
1831
|
+
};
|
|
1832
|
+
/**
|
|
1833
|
+
* Developer Mode page
|
|
1834
|
+
* @name get/dev
|
|
1835
|
+
* @function
|
|
1836
|
+
* @memberof module:routes/admin~routes/adminRouter
|
|
1837
|
+
*/
|
|
1838
|
+
router.get(
|
|
1839
|
+
"/dev",
|
|
1840
|
+
isAdmin,
|
|
1841
|
+
error_catcher(async (req, res) => {
|
|
1842
|
+
const form = await dev_form(req);
|
|
1843
|
+
send_admin_page({
|
|
1844
|
+
res,
|
|
1845
|
+
req,
|
|
1846
|
+
active_sub: "Development",
|
|
1847
|
+
contents: {
|
|
1848
|
+
type: "card",
|
|
1849
|
+
title: req.__("Development settings"),
|
|
1850
|
+
contents: [
|
|
1851
|
+
renderForm(form, req.csrfToken())/*,
|
|
1852
|
+
a(
|
|
1853
|
+
{
|
|
1854
|
+
id: "testemail",
|
|
1855
|
+
href: "/admin/send-test-email",
|
|
1856
|
+
class: "btn btn-primary",
|
|
1857
|
+
},
|
|
1858
|
+
req.__("Send test email")
|
|
1859
|
+
),*/
|
|
1860
|
+
],
|
|
1861
|
+
},
|
|
1862
|
+
});
|
|
1863
|
+
})
|
|
1864
|
+
);
|
|
1865
|
+
|
|
1866
|
+
/**
|
|
1867
|
+
* Development mode
|
|
1868
|
+
* @name post/email
|
|
1869
|
+
* @function
|
|
1870
|
+
* @memberof module:routes/admin~routes/adminRouter
|
|
1871
|
+
*/
|
|
1872
|
+
router.post(
|
|
1873
|
+
"/dev",
|
|
1874
|
+
isAdmin,
|
|
1875
|
+
error_catcher(async (req, res) => {
|
|
1876
|
+
const form = await dev_form(req);
|
|
1877
|
+
form.validate(req.body);
|
|
1878
|
+
if (form.hasErrors) {
|
|
1879
|
+
send_admin_page({
|
|
1880
|
+
res,
|
|
1881
|
+
req,
|
|
1882
|
+
active_sub: "Development",
|
|
1883
|
+
contents: {
|
|
1884
|
+
type: "card",
|
|
1885
|
+
title: req.__("Development settings"),
|
|
1886
|
+
contents: [renderForm(form, req.csrfToken())],
|
|
1887
|
+
},
|
|
1888
|
+
});
|
|
1889
|
+
} else {
|
|
1890
|
+
await save_config_from_form(form);
|
|
1891
|
+
req.flash("success", req.__("Development mode settings updated"));
|
|
1892
|
+
if (!req.xhr) res.redirect("/admin/dev");
|
|
1893
|
+
else res.json({ success: "ok" });
|
|
1894
|
+
}
|
|
1895
|
+
})
|
|
1896
|
+
);
|
package/routes/api.js
CHANGED
|
@@ -124,8 +124,17 @@ router.post(
|
|
|
124
124
|
error_catcher(async (req, res, next) => {
|
|
125
125
|
let { viewName, queryName } = req.params;
|
|
126
126
|
const view = await View.findOne({ name: viewName });
|
|
127
|
+
const db = require("@saltcorn/data/db");
|
|
127
128
|
if (!view) {
|
|
128
|
-
res.status(404).json({
|
|
129
|
+
res.status(404).json({
|
|
130
|
+
error: req.__("View %s not found", viewName),
|
|
131
|
+
view: viewName,
|
|
132
|
+
queryName: queryName,
|
|
133
|
+
smr: req.smr,
|
|
134
|
+
smrHeader: req.headers["x-saltcorn-client"],
|
|
135
|
+
schema: db.getTenantSchema(),
|
|
136
|
+
userTenant: req.user?.tenant,
|
|
137
|
+
});
|
|
129
138
|
return;
|
|
130
139
|
}
|
|
131
140
|
await passport.authenticate(
|
|
@@ -143,7 +152,15 @@ router.post(
|
|
|
143
152
|
const resp = await queries[queryName](...args, true);
|
|
144
153
|
res.json({ success: resp, alerts: getFlashes(req) });
|
|
145
154
|
} else {
|
|
146
|
-
res.status(404).json({
|
|
155
|
+
res.status(404).json({
|
|
156
|
+
error: req.__("Query %s not found", queryName),
|
|
157
|
+
view: viewName,
|
|
158
|
+
queryName: queryName,
|
|
159
|
+
smr: req.smr,
|
|
160
|
+
smrHeader: req.headers["x-saltcorn-client"],
|
|
161
|
+
schema: db.getTenantSchema(),
|
|
162
|
+
userTenant: req.user?.tenant,
|
|
163
|
+
});
|
|
147
164
|
}
|
|
148
165
|
} else {
|
|
149
166
|
res.status(401).json({ error: req.__("Not authorized") });
|