@nocobase/plugin-multi-portal 2.2.0-alpha.10 → 2.2.0-alpha.12
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/README.md +12 -6
- package/dist/client-v2/354.7be267ae5d61a878.js +10 -0
- package/dist/client-v2/556.5faf86d4f8faef40.js +10 -0
- package/dist/client-v2/778.c98e7ec542d931fe.js +10 -0
- package/dist/client-v2/RootLanding.d.ts +19 -0
- package/dist/client-v2/index.js +1 -1
- package/dist/client-v2/layoutRegistration.d.ts +2 -1
- package/dist/client-v2/models/MultiPortalMobilePageModels.d.ts +3 -11
- package/dist/client-v2/pages/MultiPortalsPage.d.ts +24 -0
- package/dist/client-v2/pages/PortalRoutesDrawer.d.ts +13 -0
- package/dist/client-v2/permissions/MultiPortalPermissionsTab.d.ts +1 -0
- package/dist/client-v2/routeRepositoryScope.d.ts +5 -2
- package/dist/constants.d.ts +11 -0
- package/dist/constants.js +48 -0
- package/dist/externalVersion.js +10 -9
- package/dist/locale/en-US.json +52 -1
- package/dist/locale/zh-CN.json +79 -28
- package/dist/node_modules/tar/package.json +1 -1
- package/dist/server/collections/desktopRoutes.js +1 -0
- package/dist/server/migrations/20260730120000-fix-fixed-layout-portal-type.d.ts +13 -0
- package/dist/server/migrations/20260730120000-fix-fixed-layout-portal-type.js +73 -0
- package/dist/server/plugin.d.ts +1 -1
- package/dist/server/plugin.js +1095 -272
- package/package.json +6 -7
- package/dist/client-v2/354.ead3e96143164cd5.js +0 -10
- package/dist/client-v2/778.23ba409a924ee34a.js +0 -10
- package/dist/client-v2/845.f2b0ce44ae4be707.js +0 -10
package/dist/server/plugin.js
CHANGED
|
@@ -41,6 +41,7 @@ __export(plugin_exports, {
|
|
|
41
41
|
});
|
|
42
42
|
module.exports = __toCommonJS(plugin_exports);
|
|
43
43
|
var import_server = require("@nocobase/server");
|
|
44
|
+
var import_database = require("@nocobase/database");
|
|
44
45
|
var import_utils = require("@nocobase/utils");
|
|
45
46
|
var import_child_process = require("child_process");
|
|
46
47
|
var import_fs = __toESM(require("fs"));
|
|
@@ -51,6 +52,7 @@ var import_promises = require("stream/promises");
|
|
|
51
52
|
var tar = __toESM(require("tar"));
|
|
52
53
|
var import_zlib = require("zlib");
|
|
53
54
|
var import_ensureDefaultRoleMultiPortalAccess = require("./ensureDefaultRoleMultiPortalAccess");
|
|
55
|
+
var import_constants = require("../constants");
|
|
54
56
|
const MULTI_PORTAL_RUNTIME_FIELDS = [
|
|
55
57
|
"uid",
|
|
56
58
|
"title",
|
|
@@ -79,8 +81,8 @@ const MAIN_APP_NAME = "main";
|
|
|
79
81
|
const UNION_ROLE_KEY = "__union__";
|
|
80
82
|
const MULTI_PORTAL_MANIFEST_NAMESPACE = "multi-portal";
|
|
81
83
|
const MULTI_PORTAL_MANIFEST_SYNC_MESSAGE_TYPE = "multi-portal:app-manifest-changed";
|
|
82
|
-
const DEFAULT_INIT_PORTAL_TYPE = "
|
|
83
|
-
const DEFAULT_INIT_PORTAL_NAME = "
|
|
84
|
+
const DEFAULT_INIT_PORTAL_TYPE = "ai";
|
|
85
|
+
const DEFAULT_INIT_PORTAL_NAME = "main";
|
|
84
86
|
const DEFAULT_INIT_PORTAL_TEMPLATE = "@nocobase/portal-template-default";
|
|
85
87
|
const PORTAL_CLIENT_PREFIX = "x";
|
|
86
88
|
const PORTAL_DEPLOY_UPLOAD_LIMIT = 200 * 1024 * 1024;
|
|
@@ -91,8 +93,6 @@ const PORTAL_TEMPLATE_NPM_PACK_TIMEOUT_MS = 3e4;
|
|
|
91
93
|
const DEFAULT_MULTI_PORTAL_UID = "__default_portal__";
|
|
92
94
|
const MULTI_PORTAL_SLUG_PATTERN = /^[a-z0-9_-]+$/;
|
|
93
95
|
const INIT_PORTAL_TYPES = ["no-code", "ai"];
|
|
94
|
-
const DEFAULT_MULTI_PORTAL_UIDS = [DEFAULT_MULTI_PORTAL_UID];
|
|
95
|
-
const DEFAULT_MULTI_PORTAL_UID_SET = new Set(DEFAULT_MULTI_PORTAL_UIDS);
|
|
96
96
|
const MULTI_PORTAL_MANAGEMENT_ACTIONS = [
|
|
97
97
|
"multiPortals:list",
|
|
98
98
|
"multiPortals:get",
|
|
@@ -103,14 +103,22 @@ const MULTI_PORTAL_MANAGEMENT_ACTIONS = [
|
|
|
103
103
|
"multiPortals:destroy",
|
|
104
104
|
"multiPortals:deploy",
|
|
105
105
|
"multiPortals:pullSource",
|
|
106
|
-
"multiPortals:pushSource"
|
|
106
|
+
"multiPortals:pushSource",
|
|
107
|
+
"desktopRoutes:list",
|
|
108
|
+
"desktopRoutes:get",
|
|
109
|
+
"desktopRoutes:create",
|
|
110
|
+
"desktopRoutes:update",
|
|
111
|
+
"desktopRoutes:move",
|
|
112
|
+
"desktopRoutes:destroy",
|
|
113
|
+
"desktopRoutes:updateOrCreate",
|
|
114
|
+
"registry:list",
|
|
115
|
+
"registry:get"
|
|
107
116
|
];
|
|
108
117
|
const ROLE_MULTI_PORTAL_PERMISSION_ACTIONS = [
|
|
109
118
|
"roles.multiPortals:*",
|
|
110
119
|
"rolesMultiPortalDesktopRoutes:*",
|
|
111
120
|
"rolesMultiPortalRoutePolicies:*"
|
|
112
121
|
];
|
|
113
|
-
const EXPLICIT_DESKTOP_ROUTE_UI_LAYOUTS = Symbol("explicitDesktopRouteUiLayouts");
|
|
114
122
|
const SKIP_CREATE_PORTAL_DIRECTORY = Symbol("skipCreatePortalDirectory");
|
|
115
123
|
function getRecordField(record, field) {
|
|
116
124
|
if (!record || typeof record !== "object") {
|
|
@@ -174,6 +182,38 @@ function getDefaultMultiPortalRecord() {
|
|
|
174
182
|
uiLayoutUid: "admin-layout-model"
|
|
175
183
|
};
|
|
176
184
|
}
|
|
185
|
+
function getFreshMultiPortalRecords() {
|
|
186
|
+
return [getDefaultMultiPortalRecord()];
|
|
187
|
+
}
|
|
188
|
+
function getFixedLayoutMultiPortalRecords() {
|
|
189
|
+
return [
|
|
190
|
+
{
|
|
191
|
+
uid: import_constants.DEFAULT_ADMIN_MULTI_PORTAL_UID,
|
|
192
|
+
title: "Desktop layout",
|
|
193
|
+
icon: "DesktopOutlined",
|
|
194
|
+
portalType: "no-code",
|
|
195
|
+
portalName: "admin",
|
|
196
|
+
routePath: "/admin",
|
|
197
|
+
authCheck: true,
|
|
198
|
+
enabled: true,
|
|
199
|
+
uiLayoutUid: "admin-layout-model"
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
uid: import_constants.DEFAULT_MOBILE_MULTI_PORTAL_UID,
|
|
203
|
+
title: "Mobile layout",
|
|
204
|
+
icon: "MobileOutlined",
|
|
205
|
+
portalType: "no-code",
|
|
206
|
+
portalName: "mobile",
|
|
207
|
+
routePath: "/mobile",
|
|
208
|
+
authCheck: true,
|
|
209
|
+
enabled: true,
|
|
210
|
+
uiLayoutUid: "mobile-layout-model"
|
|
211
|
+
}
|
|
212
|
+
];
|
|
213
|
+
}
|
|
214
|
+
function getFixedLayoutMultiPortalRecord(uid) {
|
|
215
|
+
return getFixedLayoutMultiPortalRecords().find((portal) => portal.uid === uid);
|
|
216
|
+
}
|
|
177
217
|
function isValidPortalDeploySegment(value) {
|
|
178
218
|
return /^[A-Za-z0-9_-]+$/.test(value);
|
|
179
219
|
}
|
|
@@ -197,6 +237,20 @@ function joinPortalStoragePublicPath(publicPath, pathname) {
|
|
|
197
237
|
function isAbsoluteUrl(value) {
|
|
198
238
|
return /^[a-z][a-z\d+\-.]*:\/\//i.test(value) || value.startsWith("//");
|
|
199
239
|
}
|
|
240
|
+
function normalizePortalStorageUrlPathname(pathname) {
|
|
241
|
+
const normalized = pathname.replace(/\/+/g, "/");
|
|
242
|
+
return normalized === "/" ? normalized : normalized.replace(/\/+$/, "");
|
|
243
|
+
}
|
|
244
|
+
function resolvePortalStorageEnvApiUrl(apiUrl) {
|
|
245
|
+
try {
|
|
246
|
+
const parsedUrl = new URL(apiUrl.startsWith("//") ? `http:${apiUrl}` : apiUrl);
|
|
247
|
+
return normalizePortalStorageUrlPathname(parsedUrl.pathname);
|
|
248
|
+
} catch {
|
|
249
|
+
const [pathname] = apiUrl.split(/[?#]/, 1);
|
|
250
|
+
const withLeadingSlash = (pathname == null ? void 0 : pathname.startsWith("/")) ? pathname : `/${pathname || "api"}`;
|
|
251
|
+
return normalizePortalStorageUrlPathname(withLeadingSlash);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
200
254
|
function appendPortalStorageSubAppApiUrl(apiUrl, appName) {
|
|
201
255
|
if (appName === MAIN_APP_NAME) {
|
|
202
256
|
return apiUrl;
|
|
@@ -387,6 +441,73 @@ async function copyPortalTemplate(sourceDir, targetDir) {
|
|
|
387
441
|
filter: (source) => !import_path.default.relative(sourceDir, source).split(import_path.default.sep).some((segment) => segment.startsWith("._") || ignoredSegments.has(segment))
|
|
388
442
|
});
|
|
389
443
|
}
|
|
444
|
+
function getPortalStorageConfig(options) {
|
|
445
|
+
const sourceOptions = isRecordLike(options) ? options : {};
|
|
446
|
+
const sourceStorage = trimString(sourceOptions.sourceStorage);
|
|
447
|
+
if (sourceStorage !== "git") {
|
|
448
|
+
return { sourceStorage: "nocobase" };
|
|
449
|
+
}
|
|
450
|
+
const gitOptions = isRecordLike(sourceOptions.git) ? sourceOptions.git : {};
|
|
451
|
+
return {
|
|
452
|
+
sourceStorage,
|
|
453
|
+
git: {
|
|
454
|
+
repo: trimString(gitOptions.repo),
|
|
455
|
+
branch: trimString(gitOptions.branch) || "main",
|
|
456
|
+
path: trimString(gitOptions.path) || "."
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
function upsertPortalEnvContent(content, values) {
|
|
461
|
+
const nextValues = { ...values };
|
|
462
|
+
const lines = content ? content.replace(/\r\n/g, "\n").split("\n") : [];
|
|
463
|
+
const result = [];
|
|
464
|
+
for (const line of lines) {
|
|
465
|
+
if (!line && result.length === lines.length - 1) {
|
|
466
|
+
continue;
|
|
467
|
+
}
|
|
468
|
+
const match = line.match(/^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
469
|
+
const key = match == null ? void 0 : match[2];
|
|
470
|
+
if (key && Object.prototype.hasOwnProperty.call(nextValues, key)) {
|
|
471
|
+
result.push(`${key}=${nextValues[key]}`);
|
|
472
|
+
delete nextValues[key];
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
result.push(line);
|
|
476
|
+
}
|
|
477
|
+
for (const [key, value] of Object.entries(nextValues)) {
|
|
478
|
+
result.push(`${key}=${value}`);
|
|
479
|
+
}
|
|
480
|
+
return `${result.join("\n").replace(/\n*$/, "")}
|
|
481
|
+
`;
|
|
482
|
+
}
|
|
483
|
+
async function upsertPortalEnvFile(filePath, values) {
|
|
484
|
+
let content = "";
|
|
485
|
+
try {
|
|
486
|
+
content = await import_fs.default.promises.readFile(filePath, "utf-8");
|
|
487
|
+
} catch {
|
|
488
|
+
content = "";
|
|
489
|
+
}
|
|
490
|
+
await import_fs.default.promises.writeFile(filePath, upsertPortalEnvContent(content, values), "utf-8");
|
|
491
|
+
}
|
|
492
|
+
async function ensurePortalStorageConfigFiles(portalDir, item) {
|
|
493
|
+
const apiUrl = getPortalStorageApiUrl(item.appName);
|
|
494
|
+
const portalBase = getPortalDeployBasePath(item.appName, item.portalName);
|
|
495
|
+
await import_fs.default.promises.mkdir(portalDir, { recursive: true });
|
|
496
|
+
await upsertPortalEnvFile(import_path.default.join(portalDir, ".env"), {
|
|
497
|
+
NOCOBASE_API_URL: resolvePortalStorageEnvApiUrl(apiUrl),
|
|
498
|
+
NOCOBASE_PORTAL_BASE: portalBase
|
|
499
|
+
});
|
|
500
|
+
await upsertPortalEnvFile(import_path.default.join(portalDir, ".env.local"), {
|
|
501
|
+
NOCOBASE_API_URL: apiUrl,
|
|
502
|
+
NOCOBASE_PORTAL_BASE: portalBase
|
|
503
|
+
});
|
|
504
|
+
await import_fs.default.promises.writeFile(
|
|
505
|
+
import_path.default.join(portalDir, "portal.config.json"),
|
|
506
|
+
`${JSON.stringify(item.config, null, 2)}
|
|
507
|
+
`,
|
|
508
|
+
"utf-8"
|
|
509
|
+
);
|
|
510
|
+
}
|
|
390
511
|
function sanitizePortalStorageNodeOptions(value) {
|
|
391
512
|
return trimString(value).split(/\s+/).filter((option) => option !== "--preserve-symlinks" && option !== "--preserve-symlinks-main").join(" ");
|
|
392
513
|
}
|
|
@@ -475,7 +596,8 @@ async function buildPortalStorageItem(portalDir, item) {
|
|
|
475
596
|
const logPath = getPortalStorageLogPath(item);
|
|
476
597
|
const buildEnv = getPortalStorageCommandEnv({
|
|
477
598
|
NOCOBASE_API_URL: getPortalStorageApiUrl(item.appName),
|
|
478
|
-
NOCOBASE_PORTAL_BASE: getPortalDeployBasePath(item.appName, item.portalName)
|
|
599
|
+
NOCOBASE_PORTAL_BASE: getPortalDeployBasePath(item.appName, item.portalName),
|
|
600
|
+
COREPACK_ENABLE_PROJECT_SPEC: "0"
|
|
479
601
|
});
|
|
480
602
|
await appendPortalStorageLog(logPath, `Building portal ${item.appName}/${item.portalName}.`);
|
|
481
603
|
await appendPortalStorageLog(
|
|
@@ -709,29 +831,6 @@ async function replacePortalSourceFromArchive(params) {
|
|
|
709
831
|
await import_fs.default.promises.rm(tarPath, { force: true });
|
|
710
832
|
}
|
|
711
833
|
}
|
|
712
|
-
function isDefaultMultiPortalUid(uid) {
|
|
713
|
-
return typeof uid === "string" && DEFAULT_MULTI_PORTAL_UID_SET.has(uid);
|
|
714
|
-
}
|
|
715
|
-
function getDefaultMultiPortalRecordForUid(uid) {
|
|
716
|
-
return isDefaultMultiPortalUid(uid) ? getDefaultMultiPortalRecord() : void 0;
|
|
717
|
-
}
|
|
718
|
-
function getDefaultMultiPortalUiLayoutUid(portal) {
|
|
719
|
-
return Object.prototype.hasOwnProperty.call(portal, "uiLayoutUid") ? portal.uiLayoutUid ?? null : null;
|
|
720
|
-
}
|
|
721
|
-
function withCustomMultiPortalFilter(filter = {}) {
|
|
722
|
-
const customFilter = { ...filter };
|
|
723
|
-
const uidFilter = customFilter.uid;
|
|
724
|
-
if (Array.isArray(uidFilter)) {
|
|
725
|
-
customFilter.uid = uidFilter.filter((uid) => !isDefaultMultiPortalUid(uid));
|
|
726
|
-
return customFilter;
|
|
727
|
-
}
|
|
728
|
-
if (isDefaultMultiPortalUid(uidFilter)) {
|
|
729
|
-
customFilter.uid = [];
|
|
730
|
-
return customFilter;
|
|
731
|
-
}
|
|
732
|
-
customFilter["uid.$notIn"] = DEFAULT_MULTI_PORTAL_UIDS;
|
|
733
|
-
return customFilter;
|
|
734
|
-
}
|
|
735
834
|
function uniqueDesktopRouteIds(routeIds) {
|
|
736
835
|
const seen = /* @__PURE__ */ new Set();
|
|
737
836
|
return routeIds.filter((routeId) => {
|
|
@@ -786,14 +885,25 @@ function getExplicitRequestedLayoutUid(layout) {
|
|
|
786
885
|
return uid;
|
|
787
886
|
}
|
|
788
887
|
}
|
|
888
|
+
function hasActionParam(params, key) {
|
|
889
|
+
return isRecordLike(params) && Object.prototype.hasOwnProperty.call(params, key);
|
|
890
|
+
}
|
|
789
891
|
function getRequestedMultiPortalUid(ctx) {
|
|
790
|
-
var _a, _b;
|
|
791
|
-
const
|
|
792
|
-
const
|
|
793
|
-
|
|
892
|
+
var _a, _b, _c;
|
|
893
|
+
const hasPortalScope = hasActionParam((_a = ctx.action) == null ? void 0 : _a.params, "portal");
|
|
894
|
+
const hasLayoutScope = hasActionParam((_b = ctx.action) == null ? void 0 : _b.params, "layout");
|
|
895
|
+
const portalUid = getExplicitRequestedLayoutUid((_c = ctx.action) == null ? void 0 : _c.params.portal);
|
|
896
|
+
if (hasPortalScope && hasLayoutScope) {
|
|
794
897
|
ctx.throw(400, "layout and portal cannot be used together");
|
|
795
898
|
return;
|
|
796
899
|
}
|
|
900
|
+
if (!hasPortalScope) {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
if (!portalUid) {
|
|
904
|
+
ctx.throw(400, "Invalid portal scope");
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
797
907
|
return portalUid;
|
|
798
908
|
}
|
|
799
909
|
function getCurrentRoles(ctx) {
|
|
@@ -818,27 +928,88 @@ function getMultiPortalNameFromValues(ctx) {
|
|
|
818
928
|
return portalName;
|
|
819
929
|
}
|
|
820
930
|
}
|
|
821
|
-
function
|
|
931
|
+
function getMultiPortalWriteRecords(ctx) {
|
|
822
932
|
var _a;
|
|
823
933
|
const values = (_a = ctx.action) == null ? void 0 : _a.params.values;
|
|
824
|
-
|
|
825
|
-
|
|
934
|
+
const records = Array.isArray(values) ? values : [values];
|
|
935
|
+
return records.filter(
|
|
936
|
+
(record) => !!record && typeof record === "object" && !Array.isArray(record)
|
|
937
|
+
);
|
|
938
|
+
}
|
|
939
|
+
async function getMultiPortalWriteTargets(ctx, fields) {
|
|
940
|
+
var _a, _b, _c, _d, _e;
|
|
941
|
+
const records = getMultiPortalWriteRecords(ctx);
|
|
942
|
+
if (!records.length || ((_a = ctx.action) == null ? void 0 : _a.actionName) === "create") {
|
|
943
|
+
return records.map((values2) => ({ values: values2 }));
|
|
944
|
+
}
|
|
945
|
+
const repository = ctx.db.getRepository("multiPortals");
|
|
946
|
+
const actionName = (_b = ctx.action) == null ? void 0 : _b.actionName;
|
|
947
|
+
const requestedFilterKeys = (_c = ctx.action) == null ? void 0 : _c.params.filterKeys;
|
|
948
|
+
if ((actionName === "firstOrCreate" || actionName === "updateOrCreate") && Array.isArray(requestedFilterKeys) && requestedFilterKeys.every((filterKey) => typeof filterKey === "string")) {
|
|
949
|
+
const targets = [];
|
|
950
|
+
for (const values2 of records) {
|
|
951
|
+
const existing2 = await repository.findOne({
|
|
952
|
+
filter: import_database.Repository.valuesToFilter(values2, requestedFilterKeys),
|
|
953
|
+
fields
|
|
954
|
+
});
|
|
955
|
+
targets.push({ existing: existing2, values: values2 });
|
|
956
|
+
}
|
|
957
|
+
return targets;
|
|
958
|
+
}
|
|
959
|
+
if (records.length > 1) {
|
|
960
|
+
const targets = [];
|
|
961
|
+
for (const values2 of records) {
|
|
962
|
+
const uid2 = values2.uid;
|
|
963
|
+
const existing2 = typeof uid2 === "string" && uid2 ? await repository.findOne({
|
|
964
|
+
filterByTk: uid2,
|
|
965
|
+
fields
|
|
966
|
+
}) : null;
|
|
967
|
+
targets.push({ existing: existing2, values: values2 });
|
|
968
|
+
}
|
|
969
|
+
return targets;
|
|
826
970
|
}
|
|
827
|
-
|
|
971
|
+
const values = records[0];
|
|
972
|
+
const filterByTk = (_d = ctx.action) == null ? void 0 : _d.params.filterByTk;
|
|
973
|
+
if (filterByTk !== void 0 && filterByTk !== null) {
|
|
974
|
+
const existingRecords = await repository.find({
|
|
975
|
+
filterByTk,
|
|
976
|
+
fields
|
|
977
|
+
});
|
|
978
|
+
return existingRecords.length ? existingRecords.map((existing2) => ({ existing: existing2, values })) : [{ values }];
|
|
979
|
+
}
|
|
980
|
+
const filter = (_e = ctx.action) == null ? void 0 : _e.params.filter;
|
|
981
|
+
if (isRecordLike(filter) && !Array.isArray(filter)) {
|
|
982
|
+
const existingRecords = await repository.find({
|
|
983
|
+
filter,
|
|
984
|
+
fields
|
|
985
|
+
});
|
|
986
|
+
return existingRecords.length ? existingRecords.map((existing2) => ({ existing: existing2, values })) : [{ values }];
|
|
987
|
+
}
|
|
988
|
+
const uid = values.uid;
|
|
989
|
+
const existing = typeof uid === "string" && uid ? await repository.findOne({
|
|
990
|
+
filterByTk: uid,
|
|
991
|
+
fields
|
|
992
|
+
}) : null;
|
|
993
|
+
return [{ existing, values }];
|
|
828
994
|
}
|
|
829
995
|
function isTruthyActionBoolean(value) {
|
|
830
996
|
return value === true || value === 1 || value === "1" || value === "true";
|
|
831
997
|
}
|
|
832
998
|
async function captureSkipCreatePortalDirectory(ctx, next) {
|
|
833
|
-
const
|
|
834
|
-
|
|
999
|
+
const records = getMultiPortalWriteRecords(ctx);
|
|
1000
|
+
const recordsWithOption = records.filter(
|
|
1001
|
+
(record) => Object.prototype.hasOwnProperty.call(record, "skipCreatePortalDirectory")
|
|
1002
|
+
);
|
|
1003
|
+
if (!recordsWithOption.length) {
|
|
835
1004
|
await next();
|
|
836
1005
|
return;
|
|
837
1006
|
}
|
|
838
|
-
ctx[SKIP_CREATE_PORTAL_DIRECTORY] =
|
|
839
|
-
|
|
1007
|
+
ctx[SKIP_CREATE_PORTAL_DIRECTORY] = recordsWithOption.some(
|
|
1008
|
+
(record) => isTruthyActionBoolean(record.skipCreatePortalDirectory)
|
|
840
1009
|
);
|
|
841
|
-
|
|
1010
|
+
for (const record of recordsWithOption) {
|
|
1011
|
+
delete record.skipCreatePortalDirectory;
|
|
1012
|
+
}
|
|
842
1013
|
await next();
|
|
843
1014
|
}
|
|
844
1015
|
function shouldSkipCreatePortalDirectory(options) {
|
|
@@ -846,156 +1017,180 @@ function shouldSkipCreatePortalDirectory(options) {
|
|
|
846
1017
|
return ((_a = options == null ? void 0 : options.context) == null ? void 0 : _a[SKIP_CREATE_PORTAL_DIRECTORY]) === true;
|
|
847
1018
|
}
|
|
848
1019
|
async function normalizeMultiPortalSlugValues(ctx, next) {
|
|
849
|
-
const values
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
1020
|
+
for (const values of getMultiPortalWriteRecords(ctx)) {
|
|
1021
|
+
const portalName = values.portalName;
|
|
1022
|
+
if (portalName === void 0 || portalName === null || typeof portalName !== "string") {
|
|
1023
|
+
continue;
|
|
1024
|
+
}
|
|
1025
|
+
const slug = portalName.trim();
|
|
1026
|
+
if (!slug) {
|
|
1027
|
+
continue;
|
|
1028
|
+
}
|
|
1029
|
+
if (!MULTI_PORTAL_SLUG_PATTERN.test(slug)) {
|
|
1030
|
+
ctx.throw(400, "Portal name can only contain lowercase letters, numbers, hyphens, and underscores");
|
|
1031
|
+
return;
|
|
1032
|
+
}
|
|
1033
|
+
values.portalName = slug;
|
|
1034
|
+
values.routePath = `/${slug}`;
|
|
858
1035
|
}
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
1036
|
+
await next();
|
|
1037
|
+
}
|
|
1038
|
+
function isUniqueConstraintViolation(error) {
|
|
1039
|
+
return error instanceof import_database.UniqueConstraintError || isRecordLike(error) && error.name === "SequelizeUniqueConstraintError";
|
|
1040
|
+
}
|
|
1041
|
+
async function createDefaultMultiPortalBestEffort(db, portal) {
|
|
1042
|
+
const repository = db.getRepository("multiPortals");
|
|
1043
|
+
const [uidConflict, portalNameConflict] = await Promise.all([
|
|
1044
|
+
repository.findOne({
|
|
1045
|
+
filterByTk: portal.uid,
|
|
1046
|
+
fields: ["uid"]
|
|
1047
|
+
}),
|
|
1048
|
+
repository.findOne({
|
|
1049
|
+
filter: { portalName: portal.portalName },
|
|
1050
|
+
fields: ["uid"]
|
|
1051
|
+
})
|
|
1052
|
+
]);
|
|
1053
|
+
if (uidConflict || portalNameConflict) {
|
|
1054
|
+
return false;
|
|
863
1055
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
return;
|
|
1056
|
+
try {
|
|
1057
|
+
await repository.create({ values: portal });
|
|
1058
|
+
return true;
|
|
1059
|
+
} catch (error) {
|
|
1060
|
+
if (isUniqueConstraintViolation(error)) {
|
|
1061
|
+
return false;
|
|
1062
|
+
}
|
|
1063
|
+
throw error;
|
|
867
1064
|
}
|
|
868
|
-
values.portalName = slug;
|
|
869
|
-
values.routePath = `/${slug}`;
|
|
870
|
-
await next();
|
|
871
1065
|
}
|
|
872
|
-
async function
|
|
1066
|
+
async function repairFixedLayoutMultiPortalRecords(db) {
|
|
873
1067
|
const repository = db.getRepository("multiPortals");
|
|
874
|
-
const
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
transaction: options == null ? void 0 : options.transaction
|
|
879
|
-
});
|
|
880
|
-
if (!existing) {
|
|
881
|
-
await repository.create({
|
|
882
|
-
values: defaultPortal,
|
|
883
|
-
transaction: options == null ? void 0 : options.transaction
|
|
1068
|
+
for (const portal of getFixedLayoutMultiPortalRecords()) {
|
|
1069
|
+
const existing = await repository.findOne({
|
|
1070
|
+
filterByTk: portal.uid,
|
|
1071
|
+
fields: ["uid", "portalType", "portalName", "routePath", "uiLayoutUid"]
|
|
884
1072
|
});
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
uid: defaultPortal.uid,
|
|
889
|
-
portalType: defaultPortal.portalType ?? null,
|
|
890
|
-
portalName: defaultPortal.portalName,
|
|
891
|
-
routePath: defaultPortal.routePath,
|
|
892
|
-
authCheck: defaultPortal.authCheck,
|
|
893
|
-
uiLayoutUid: getDefaultMultiPortalUiLayoutUid(defaultPortal)
|
|
894
|
-
};
|
|
895
|
-
const shouldRepair = Object.entries(protectedValues).some(([field, value]) => existing.get(field) !== value);
|
|
896
|
-
if (shouldRepair) {
|
|
1073
|
+
if ((existing == null ? void 0 : existing.get("portalName")) !== portal.portalName || existing.get("routePath") !== portal.routePath || existing.get("uiLayoutUid") !== portal.uiLayoutUid || existing.get("portalType") === portal.portalType) {
|
|
1074
|
+
continue;
|
|
1075
|
+
}
|
|
897
1076
|
await repository.update({
|
|
898
|
-
filterByTk:
|
|
899
|
-
values:
|
|
900
|
-
|
|
1077
|
+
filterByTk: portal.uid,
|
|
1078
|
+
values: {
|
|
1079
|
+
portalType: portal.portalType
|
|
1080
|
+
}
|
|
901
1081
|
});
|
|
902
1082
|
}
|
|
903
1083
|
}
|
|
904
|
-
async function
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
if (!defaultPortal) {
|
|
908
|
-
await next();
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
const params = (_b = ctx.action) == null ? void 0 : _b.params;
|
|
912
|
-
const rawValues = isRecordLike(params == null ? void 0 : params.values) ? params.values : {};
|
|
913
|
-
const values = {
|
|
914
|
-
uid: defaultPortal.uid,
|
|
915
|
-
portalType: defaultPortal.portalType ?? null,
|
|
916
|
-
portalName: defaultPortal.portalName,
|
|
917
|
-
routePath: defaultPortal.routePath,
|
|
918
|
-
authCheck: defaultPortal.authCheck,
|
|
919
|
-
uiLayoutUid: getDefaultMultiPortalUiLayoutUid(defaultPortal)
|
|
920
|
-
};
|
|
921
|
-
if (Object.prototype.hasOwnProperty.call(rawValues, "title")) {
|
|
922
|
-
values.title = rawValues.title;
|
|
1084
|
+
async function seedFreshMultiPortals(db) {
|
|
1085
|
+
for (const portal of getFreshMultiPortalRecords()) {
|
|
1086
|
+
await createDefaultMultiPortalBestEffort(db, portal);
|
|
923
1087
|
}
|
|
924
|
-
|
|
925
|
-
|
|
1088
|
+
}
|
|
1089
|
+
async function seedHistoricalMultiPortals(db) {
|
|
1090
|
+
if (getInitPortalType() === "ai") {
|
|
1091
|
+
await createDefaultMultiPortalBestEffort(db, getDefaultMultiPortalRecord());
|
|
926
1092
|
}
|
|
927
|
-
|
|
928
|
-
|
|
1093
|
+
for (const portal of getFixedLayoutMultiPortalRecords()) {
|
|
1094
|
+
await createDefaultMultiPortalBestEffort(db, portal);
|
|
929
1095
|
}
|
|
930
|
-
(
|
|
931
|
-
{
|
|
932
|
-
values
|
|
933
|
-
},
|
|
934
|
-
{
|
|
935
|
-
values: "overwrite"
|
|
936
|
-
}
|
|
937
|
-
);
|
|
938
|
-
await next();
|
|
1096
|
+
await repairFixedLayoutMultiPortalRecords(db);
|
|
939
1097
|
}
|
|
940
1098
|
async function preventUiLayoutRouteNameConflict(ctx, next) {
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
1099
|
+
const targets = await getMultiPortalWriteTargets(ctx, ["uid", "portalName", "uiLayoutUid"]);
|
|
1100
|
+
for (const { existing, values } of targets) {
|
|
1101
|
+
const existingUid = trimString(existing == null ? void 0 : existing.get("uid"));
|
|
1102
|
+
const uid = trimString(values.uid ?? existingUid);
|
|
1103
|
+
const portalName = trimString(values.portalName ?? (existing == null ? void 0 : existing.get("portalName")));
|
|
1104
|
+
const uiLayoutUid = trimString(values.uiLayoutUid ?? (existing == null ? void 0 : existing.get("uiLayoutUid")));
|
|
1105
|
+
const fixedPortal = getFixedLayoutMultiPortalRecord(existingUid) || getFixedLayoutMultiPortalRecord(uid);
|
|
1106
|
+
const canonicalPortal = (fixedPortal == null ? void 0 : fixedPortal.uid) === uid && (!existingUid || fixedPortal.uid === existingUid) && fixedPortal.portalName === portalName && fixedPortal.uiLayoutUid === uiLayoutUid;
|
|
1107
|
+
if (fixedPortal && !canonicalPortal) {
|
|
1108
|
+
ctx.throw(400, "Reserved Portal UID must match its canonical route and UI layout");
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
if (!portalName) {
|
|
1112
|
+
continue;
|
|
1113
|
+
}
|
|
1114
|
+
if (portalName === "admin" || portalName === "mobile") {
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
const existingInitPortal = (existing == null ? void 0 : existing.get("uid")) === DEFAULT_MULTI_PORTAL_UID && existing.get("portalName") === portalName;
|
|
1118
|
+
const uiLayout = await ctx.db.getRepository("uiLayouts").findOne({
|
|
1119
|
+
filter: {
|
|
1120
|
+
routeName: portalName
|
|
1121
|
+
},
|
|
1122
|
+
fields: ["uid"]
|
|
1123
|
+
});
|
|
1124
|
+
if (uiLayout && !existingInitPortal) {
|
|
1125
|
+
ctx.throw(400, "Portal route name conflicts with an existing UI layout");
|
|
1126
|
+
return;
|
|
1127
|
+
}
|
|
950
1128
|
}
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1129
|
+
await next();
|
|
1130
|
+
}
|
|
1131
|
+
async function preventMultiPortalBackingLayoutChange(ctx, next) {
|
|
1132
|
+
const targets = await getMultiPortalWriteTargets(ctx, ["uiLayoutUid"]);
|
|
1133
|
+
for (const { existing, values } of targets) {
|
|
1134
|
+
if (!Object.prototype.hasOwnProperty.call(values, "uiLayoutUid")) {
|
|
1135
|
+
continue;
|
|
1136
|
+
}
|
|
1137
|
+
if (existing && existing.get("uiLayoutUid") !== values.uiLayoutUid) {
|
|
1138
|
+
ctx.throw(400, "Portal backing UI layout cannot be changed");
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
960
1141
|
}
|
|
961
1142
|
await next();
|
|
962
1143
|
}
|
|
963
|
-
async function findRequestedMultiPortal(ctx) {
|
|
964
|
-
var _a;
|
|
1144
|
+
async function findRequestedMultiPortal(ctx, transaction) {
|
|
965
1145
|
const portalUid = getRequestedMultiPortalUid(ctx);
|
|
966
1146
|
if (portalUid === void 0) {
|
|
967
1147
|
return {
|
|
968
1148
|
requested: false
|
|
969
1149
|
};
|
|
970
1150
|
}
|
|
971
|
-
const isExplicitPortalRequest = getExplicitRequestedLayoutUid((_a = ctx.action) == null ? void 0 : _a.params.portal) !== void 0;
|
|
972
|
-
if (!isExplicitPortalRequest) {
|
|
973
|
-
const uiLayout = await ctx.db.getRepository("uiLayouts").findOne({
|
|
974
|
-
filter: {
|
|
975
|
-
uid: portalUid
|
|
976
|
-
},
|
|
977
|
-
fields: ["uid"]
|
|
978
|
-
});
|
|
979
|
-
if (uiLayout) {
|
|
980
|
-
return {
|
|
981
|
-
requested: false
|
|
982
|
-
};
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
1151
|
const portal = await ctx.db.getRepository("multiPortals").findOne({
|
|
986
1152
|
filter: {
|
|
987
1153
|
uid: portalUid
|
|
988
1154
|
},
|
|
989
|
-
fields: ["uid", "uiLayoutUid", "enabled"]
|
|
1155
|
+
fields: ["uid", "portalType", "uiLayoutUid", "enabled"],
|
|
1156
|
+
...transaction ? { lock: transaction.LOCK.UPDATE } : {},
|
|
1157
|
+
transaction
|
|
990
1158
|
});
|
|
991
|
-
if (
|
|
992
|
-
return {
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
};
|
|
1159
|
+
if (!portal || portal.get("enabled") !== true) {
|
|
1160
|
+
return { requested: true };
|
|
1161
|
+
}
|
|
1162
|
+
if (portal.get("portalType") !== "no-code") {
|
|
1163
|
+
ctx.throw(400, `Portal '${portalUid}' does not support desktop routes`);
|
|
1164
|
+
}
|
|
1165
|
+
const uiLayoutUid = portal.get("uiLayoutUid");
|
|
1166
|
+
if (typeof uiLayoutUid !== "string" || !uiLayoutUid) {
|
|
1167
|
+
ctx.throw(400, `Portal '${portalUid}' has no backing UI layout`);
|
|
1168
|
+
}
|
|
1169
|
+
const uiLayout = await ctx.db.getRepository("uiLayouts").findOne({
|
|
1170
|
+
filter: {
|
|
1171
|
+
uid: uiLayoutUid,
|
|
1172
|
+
enabled: true
|
|
1173
|
+
},
|
|
1174
|
+
fields: ["uid"],
|
|
1175
|
+
...transaction ? { lock: transaction.LOCK.UPDATE } : {},
|
|
1176
|
+
transaction
|
|
1177
|
+
});
|
|
1178
|
+
if (!uiLayout) {
|
|
1179
|
+
ctx.throw(400, `Portal '${portalUid}' has no enabled backing UI layout`);
|
|
996
1180
|
}
|
|
1181
|
+
const usesLayoutPermissions = (0, import_constants.isDefaultLayoutMultiPortalUid)(portalUid);
|
|
997
1182
|
return {
|
|
998
|
-
|
|
1183
|
+
portal,
|
|
1184
|
+
requested: true,
|
|
1185
|
+
scope: {
|
|
1186
|
+
portalUid,
|
|
1187
|
+
uiLayoutUid,
|
|
1188
|
+
relation: usesLayoutPermissions ? "uiLayouts" : "multiPortals",
|
|
1189
|
+
uid: usesLayoutPermissions ? uiLayoutUid : portalUid,
|
|
1190
|
+
filter: usesLayoutPermissions ? {
|
|
1191
|
+
"uiLayouts.uid": uiLayoutUid
|
|
1192
|
+
} : getDesktopRoutePortalFilter(portalUid)
|
|
1193
|
+
}
|
|
999
1194
|
};
|
|
1000
1195
|
}
|
|
1001
1196
|
function getDesktopRoutePortalFilter(multiPortalUid) {
|
|
@@ -1006,49 +1201,90 @@ function getDesktopRoutePortalFilter(multiPortalUid) {
|
|
|
1006
1201
|
function isDesktopRouteCreateValue(value) {
|
|
1007
1202
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1008
1203
|
}
|
|
1009
|
-
function
|
|
1010
|
-
|
|
1204
|
+
function withDesktopRouteEffectiveOwner(value, scope) {
|
|
1205
|
+
if (Array.isArray(value)) {
|
|
1206
|
+
return value.map((item) => withDesktopRouteEffectiveOwner(item, scope));
|
|
1207
|
+
}
|
|
1208
|
+
if (!isDesktopRouteCreateValue(value)) {
|
|
1209
|
+
return value;
|
|
1210
|
+
}
|
|
1211
|
+
const { uiLayouts: _uiLayouts, multiPortals: _multiPortals, children, ...route } = value;
|
|
1212
|
+
return {
|
|
1213
|
+
...route,
|
|
1214
|
+
...scope.relation === "uiLayouts" ? { uiLayouts: [scope.uid] } : { multiPortals: [scope.uid] },
|
|
1215
|
+
...Array.isArray(children) ? { children: withDesktopRouteEffectiveOwner(children, scope) } : {}
|
|
1216
|
+
};
|
|
1011
1217
|
}
|
|
1012
|
-
function
|
|
1218
|
+
function collectExplicitDesktopRouteParentIds(value) {
|
|
1013
1219
|
if (Array.isArray(value)) {
|
|
1014
|
-
return value.
|
|
1220
|
+
return uniqueDesktopRouteIds(value.flatMap((item) => collectExplicitDesktopRouteParentIds(item)));
|
|
1015
1221
|
}
|
|
1016
1222
|
if (!isDesktopRouteCreateValue(value)) {
|
|
1017
|
-
return;
|
|
1223
|
+
return [];
|
|
1018
1224
|
}
|
|
1019
|
-
const
|
|
1020
|
-
if (
|
|
1021
|
-
|
|
1225
|
+
const parentIds = [];
|
|
1226
|
+
if (typeof value.parentId === "string" || typeof value.parentId === "number") {
|
|
1227
|
+
parentIds.push(value.parentId);
|
|
1022
1228
|
}
|
|
1023
1229
|
if (Array.isArray(value.children)) {
|
|
1024
|
-
|
|
1230
|
+
parentIds.push(...value.children.flatMap((child) => collectExplicitDesktopRouteParentIds(child)));
|
|
1025
1231
|
}
|
|
1026
|
-
return
|
|
1232
|
+
return uniqueDesktopRouteIds(parentIds);
|
|
1027
1233
|
}
|
|
1028
|
-
function
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
return
|
|
1234
|
+
async function assertDesktopRouteParentsBelongToScope(ctx, scope, values = ((_a) => (_a = ctx.action) == null ? void 0 : _a.params.values)(), transaction) {
|
|
1235
|
+
const parentIds = collectExplicitDesktopRouteParentIds(values);
|
|
1236
|
+
if (!parentIds.length) {
|
|
1237
|
+
return;
|
|
1032
1238
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1239
|
+
const parents = await ctx.db.getRepository("desktopRoutes").find({
|
|
1240
|
+
fields: ["id"],
|
|
1241
|
+
filter: {
|
|
1242
|
+
id: parentIds,
|
|
1243
|
+
...scope.filter
|
|
1244
|
+
},
|
|
1245
|
+
...transaction ? { lock: transaction.LOCK.UPDATE } : {},
|
|
1246
|
+
transaction
|
|
1247
|
+
});
|
|
1248
|
+
if (new Set(normalizeDesktopRouteTargetIds(parents)).size !== new Set(parentIds.map(String)).size) {
|
|
1249
|
+
ctx.throw(400, "Parent route does not belong to the requested portal scope");
|
|
1035
1250
|
}
|
|
1036
|
-
const explicitRoute = isDesktopRouteCreateValue(explicitValue) ? explicitValue : void 0;
|
|
1037
|
-
const existingMultiPortals = Array.isArray(value.multiPortals) ? value.multiPortals.filter((uid) => typeof uid === "string" && !!uid) : [];
|
|
1038
|
-
const { uiLayouts: _uiLayouts, children: _children, ...route } = value;
|
|
1039
|
-
return {
|
|
1040
|
-
...route,
|
|
1041
|
-
...hasDesktopRouteField(explicitRoute, "uiLayouts") ? { uiLayouts: explicitRoute == null ? void 0 : explicitRoute.uiLayouts } : {},
|
|
1042
|
-
multiPortals: Array.from(/* @__PURE__ */ new Set([...existingMultiPortals, multiPortalUid])),
|
|
1043
|
-
...Array.isArray(value.children) ? { children: withDesktopRouteMultiPortal(value.children, multiPortalUid, explicitRoute == null ? void 0 : explicitRoute.children) } : {}
|
|
1044
|
-
};
|
|
1045
1251
|
}
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1252
|
+
function getDesktopRouteUpdateOrCreateFilters(ctx, values = ((_b) => (_b = ctx.action) == null ? void 0 : _b.params.values)(), requestedFilterKeys = ((_c) => (_c = ctx.action) == null ? void 0 : _c.params.filterKeys)()) {
|
|
1253
|
+
const filterKeys = Array.isArray(requestedFilterKeys) ? requestedFilterKeys.filter((key) => typeof key === "string" && !!key) : [];
|
|
1254
|
+
if (!filterKeys.length) {
|
|
1255
|
+
return [];
|
|
1256
|
+
}
|
|
1257
|
+
const records = Array.isArray(values) ? values : [values];
|
|
1258
|
+
return records.flatMap((value) => {
|
|
1259
|
+
if (!isDesktopRouteCreateValue(value) || filterKeys.some((key) => !Object.prototype.hasOwnProperty.call(value, key))) {
|
|
1260
|
+
return [];
|
|
1261
|
+
}
|
|
1262
|
+
return [Object.fromEntries(filterKeys.map((key) => [key, value[key]]))];
|
|
1263
|
+
});
|
|
1264
|
+
}
|
|
1265
|
+
async function assertDesktopRouteUpsertMatchesBelongToScope(ctx, scope, values = ((_d) => (_d = ctx.action) == null ? void 0 : _d.params.values)(), filterKeys = ((_e) => (_e = ctx.action) == null ? void 0 : _e.params.filterKeys)(), transaction) {
|
|
1266
|
+
for (const filter of getDesktopRouteUpdateOrCreateFilters(ctx, values, filterKeys)) {
|
|
1267
|
+
const allMatches = await ctx.db.getRepository("desktopRoutes").find({
|
|
1268
|
+
fields: ["id"],
|
|
1269
|
+
filter,
|
|
1270
|
+
...transaction ? { lock: transaction.LOCK.UPDATE } : {},
|
|
1271
|
+
transaction
|
|
1272
|
+
});
|
|
1273
|
+
const scopedMatches = await ctx.db.getRepository("desktopRoutes").find({
|
|
1274
|
+
fields: ["id"],
|
|
1275
|
+
filter: {
|
|
1276
|
+
...filter,
|
|
1277
|
+
...scope.filter
|
|
1278
|
+
},
|
|
1279
|
+
...transaction ? { lock: transaction.LOCK.UPDATE } : {},
|
|
1280
|
+
transaction
|
|
1281
|
+
});
|
|
1282
|
+
const allMatchIds = normalizeDesktopRouteTargetIds(allMatches);
|
|
1283
|
+
const scopedMatchIds = normalizeDesktopRouteTargetIds(scopedMatches);
|
|
1284
|
+
if (allMatchIds.join(",") !== scopedMatchIds.join(",")) {
|
|
1285
|
+
ctx.throw(400, "Desktop route does not belong to the requested portal scope");
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1052
1288
|
}
|
|
1053
1289
|
async function canAccessMultiPortal(ctx, multiPortalUid) {
|
|
1054
1290
|
const currentRoles = getCurrentRoles(ctx);
|
|
@@ -1298,25 +1534,17 @@ async function prepareAccessibleDesktopRoutesForMultiPortal(ctx) {
|
|
|
1298
1534
|
if (!portalRequest.requested) {
|
|
1299
1535
|
return;
|
|
1300
1536
|
}
|
|
1301
|
-
const
|
|
1302
|
-
if (!
|
|
1537
|
+
const scope = portalRequest.scope;
|
|
1538
|
+
if (!scope) {
|
|
1303
1539
|
return null;
|
|
1304
1540
|
}
|
|
1305
|
-
|
|
1306
|
-
const uiLayoutUid = portal.get("uiLayoutUid");
|
|
1307
|
-
if (typeof portalUid !== "string" || !portalUid || typeof uiLayoutUid !== "string" || !uiLayoutUid) {
|
|
1308
|
-
return null;
|
|
1309
|
-
}
|
|
1310
|
-
if (!await canAccessMultiPortal(ctx, portalUid)) {
|
|
1541
|
+
if (scope.relation === "multiPortals" && !await canAccessMultiPortal(ctx, scope.portalUid)) {
|
|
1311
1542
|
return null;
|
|
1312
1543
|
}
|
|
1313
1544
|
if ((_a = ctx.action) == null ? void 0 : _a.params) {
|
|
1314
|
-
ctx.action.params.layout = uiLayoutUid;
|
|
1545
|
+
ctx.action.params.layout = scope.uiLayoutUid;
|
|
1315
1546
|
}
|
|
1316
|
-
return
|
|
1317
|
-
portalUid,
|
|
1318
|
-
uiLayoutUid
|
|
1319
|
-
};
|
|
1547
|
+
return scope;
|
|
1320
1548
|
}
|
|
1321
1549
|
async function addMultiPortalListAccessibleGuard(ctx, next) {
|
|
1322
1550
|
const portalContext = await prepareAccessibleDesktopRoutesForMultiPortal(ctx);
|
|
@@ -1325,10 +1553,13 @@ async function addMultiPortalListAccessibleGuard(ctx, next) {
|
|
|
1325
1553
|
return;
|
|
1326
1554
|
}
|
|
1327
1555
|
await next();
|
|
1328
|
-
if (portalContext) {
|
|
1556
|
+
if ((portalContext == null ? void 0 : portalContext.relation) === "multiPortals") {
|
|
1329
1557
|
await replaceListAccessibleRoutesWithPortalScopedRoutes(ctx, portalContext);
|
|
1330
1558
|
return;
|
|
1331
1559
|
}
|
|
1560
|
+
if ((portalContext == null ? void 0 : portalContext.relation) === "uiLayouts") {
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1332
1563
|
await removeMultiPortalOwnedRoutesFromListResponse(ctx);
|
|
1333
1564
|
}
|
|
1334
1565
|
async function addMultiPortalGetAccessibleGuard(ctx, next) {
|
|
@@ -1339,26 +1570,36 @@ async function addMultiPortalGetAccessibleGuard(ctx, next) {
|
|
|
1339
1570
|
return;
|
|
1340
1571
|
}
|
|
1341
1572
|
await next();
|
|
1342
|
-
if (portalContext) {
|
|
1573
|
+
if ((portalContext == null ? void 0 : portalContext.relation) === "multiPortals") {
|
|
1343
1574
|
await replaceGetAccessibleRouteWithPortalScopedRoute(ctx, portalContext);
|
|
1344
1575
|
return;
|
|
1345
1576
|
}
|
|
1577
|
+
if ((portalContext == null ? void 0 : portalContext.relation) === "uiLayouts") {
|
|
1578
|
+
return;
|
|
1579
|
+
}
|
|
1346
1580
|
await removeMultiPortalOwnedRouteFromGetResponse(ctx);
|
|
1347
1581
|
}
|
|
1348
1582
|
async function mapMultiPortalLayoutToUiLayoutForRolePermissionTargets(ctx, next) {
|
|
1583
|
+
var _a;
|
|
1349
1584
|
const portalRequest = await findRequestedMultiPortal(ctx);
|
|
1350
1585
|
if (portalRequest.requested && !portalRequest.portal) {
|
|
1351
1586
|
ctx.status = 200;
|
|
1352
1587
|
ctx.body = [];
|
|
1353
1588
|
return;
|
|
1354
1589
|
}
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1357
|
-
|
|
1590
|
+
const scope = portalRequest.scope;
|
|
1591
|
+
if ((scope == null ? void 0 : scope.relation) === "uiLayouts") {
|
|
1592
|
+
if ((_a = ctx.action) == null ? void 0 : _a.params) {
|
|
1593
|
+
ctx.action.params.layout = scope.uiLayoutUid;
|
|
1594
|
+
}
|
|
1595
|
+
await next();
|
|
1596
|
+
return;
|
|
1597
|
+
}
|
|
1598
|
+
if ((scope == null ? void 0 : scope.relation) === "multiPortals") {
|
|
1358
1599
|
const routes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1359
1600
|
tree: true,
|
|
1360
1601
|
sort: "sort",
|
|
1361
|
-
filter:
|
|
1602
|
+
filter: scope.filter,
|
|
1362
1603
|
fields: [...DESKTOP_ROUTE_ROLE_PERMISSION_TARGET_FIELDS]
|
|
1363
1604
|
});
|
|
1364
1605
|
ctx.status = 200;
|
|
@@ -1369,38 +1610,585 @@ async function mapMultiPortalLayoutToUiLayoutForRolePermissionTargets(ctx, next)
|
|
|
1369
1610
|
await removeMultiPortalOwnedRoutesFromListResponse(ctx);
|
|
1370
1611
|
}
|
|
1371
1612
|
async function addDesktopRouteCreateMultiPortal(ctx, next) {
|
|
1372
|
-
var _a, _b;
|
|
1613
|
+
var _a, _b, _c;
|
|
1373
1614
|
const portalRequest = await findRequestedMultiPortal(ctx);
|
|
1374
1615
|
if (portalRequest.requested && !portalRequest.portal) {
|
|
1375
1616
|
ctx.throw(400, "Invalid portal scope");
|
|
1376
1617
|
return;
|
|
1377
1618
|
}
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1619
|
+
const scope = portalRequest.scope;
|
|
1620
|
+
if (scope) {
|
|
1621
|
+
await assertDesktopRouteParentsBelongToScope(ctx, scope);
|
|
1622
|
+
if (((_a = ctx.action) == null ? void 0 : _a.actionName) === "updateOrCreate") {
|
|
1623
|
+
await assertDesktopRouteUpsertMatchesBelongToScope(ctx, scope);
|
|
1624
|
+
}
|
|
1625
|
+
(_c = ctx.action) == null ? void 0 : _c.mergeParams(
|
|
1383
1626
|
{
|
|
1384
|
-
values:
|
|
1627
|
+
values: withDesktopRouteEffectiveOwner((_b = ctx.action) == null ? void 0 : _b.params.values, scope)
|
|
1385
1628
|
},
|
|
1386
1629
|
{
|
|
1387
1630
|
values: "overwrite"
|
|
1388
1631
|
}
|
|
1389
1632
|
);
|
|
1390
1633
|
await next();
|
|
1391
|
-
const desktopRouteIds = collectDesktopRouteIds(ctx.body);
|
|
1392
|
-
await removeLayoutRoutePermissionsFromPortalOnlyRoutes(ctx, desktopRouteIds);
|
|
1393
|
-
await grantDefaultRouteAccessToNewMultiPortalRoutes(ctx, portalUid, desktopRouteIds);
|
|
1394
1634
|
return;
|
|
1395
1635
|
}
|
|
1396
1636
|
await next();
|
|
1397
1637
|
}
|
|
1638
|
+
async function addDesktopRouteEffectiveScopeFilter(ctx, next) {
|
|
1639
|
+
var _a;
|
|
1640
|
+
const portalRequest = await findRequestedMultiPortal(ctx);
|
|
1641
|
+
if (!portalRequest.requested) {
|
|
1642
|
+
await next();
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
if (!portalRequest.scope) {
|
|
1646
|
+
ctx.throw(400, "Invalid portal scope");
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
(_a = ctx.action) == null ? void 0 : _a.mergeParams({
|
|
1650
|
+
filter: portalRequest.scope.filter
|
|
1651
|
+
});
|
|
1652
|
+
await next();
|
|
1653
|
+
}
|
|
1654
|
+
function withoutDesktopRouteOwnerFields(value) {
|
|
1655
|
+
if (Array.isArray(value)) {
|
|
1656
|
+
return value.map((item) => withoutDesktopRouteOwnerFields(item));
|
|
1657
|
+
}
|
|
1658
|
+
if (!isDesktopRouteCreateValue(value)) {
|
|
1659
|
+
return value;
|
|
1660
|
+
}
|
|
1661
|
+
const { uiLayouts: _uiLayouts, multiPortals: _multiPortals, children, ...route } = value;
|
|
1662
|
+
return {
|
|
1663
|
+
...route,
|
|
1664
|
+
...Array.isArray(children) ? { children: withoutDesktopRouteOwnerFields(children) } : {}
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
function normalizeDesktopRouteTargetIds(records) {
|
|
1668
|
+
return records.map((record) => record.get("id")).filter((id) => typeof id === "string" || typeof id === "number").map(String).sort();
|
|
1669
|
+
}
|
|
1670
|
+
async function findDesktopRouteMutationTargets(ctx, scopeFilter, transaction, selector) {
|
|
1671
|
+
var _a;
|
|
1672
|
+
const params = selector ?? ((_a = ctx.action) == null ? void 0 : _a.params) ?? {};
|
|
1673
|
+
const values = params.values;
|
|
1674
|
+
const batchUpdateIds = Array.isArray(values) ? values.flatMap((value) => {
|
|
1675
|
+
if (!isDesktopRouteCreateValue(value)) {
|
|
1676
|
+
return [];
|
|
1677
|
+
}
|
|
1678
|
+
const routeId = value.id;
|
|
1679
|
+
return typeof routeId === "string" || typeof routeId === "number" ? [routeId] : [];
|
|
1680
|
+
}) : [];
|
|
1681
|
+
const hasBatchUpdateTargets = batchUpdateIds.length > 0;
|
|
1682
|
+
const filterByTk = hasBatchUpdateTargets ? batchUpdateIds : params.filterByTk;
|
|
1683
|
+
const filter = !hasBatchUpdateTargets && isRecordLike(params.filter) ? params.filter : void 0;
|
|
1684
|
+
if ((filterByTk === void 0 || filterByTk === null) && !filter) {
|
|
1685
|
+
return [];
|
|
1686
|
+
}
|
|
1687
|
+
return ctx.db.getRepository("desktopRoutes").find({
|
|
1688
|
+
fields: ["id"],
|
|
1689
|
+
...filterByTk !== void 0 && filterByTk !== null ? { filterByTk } : {},
|
|
1690
|
+
filter: {
|
|
1691
|
+
...filter ?? {},
|
|
1692
|
+
...scopeFilter ?? {}
|
|
1693
|
+
},
|
|
1694
|
+
...transaction ? { lock: transaction.LOCK.UPDATE } : {},
|
|
1695
|
+
transaction
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
async function assertDesktopRouteMutationTargetsBelongToScope(ctx, scope, transaction, selector) {
|
|
1699
|
+
const allTargets = await findDesktopRouteMutationTargets(ctx, void 0, transaction, selector);
|
|
1700
|
+
const scopedTargets = await findDesktopRouteMutationTargets(ctx, scope.filter, transaction, selector);
|
|
1701
|
+
const allTargetIds = normalizeDesktopRouteTargetIds(allTargets);
|
|
1702
|
+
const scopedTargetIds = normalizeDesktopRouteTargetIds(scopedTargets);
|
|
1703
|
+
if (!allTargetIds.length || allTargetIds.join(",") !== scopedTargetIds.join(",")) {
|
|
1704
|
+
ctx.throw(400, "Desktop route does not belong to the requested portal scope");
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
async function guardDesktopRouteUpdateScope(ctx, next) {
|
|
1708
|
+
var _a, _b;
|
|
1709
|
+
const portalRequest = await findRequestedMultiPortal(ctx);
|
|
1710
|
+
if (!portalRequest.requested) {
|
|
1711
|
+
await next();
|
|
1712
|
+
return;
|
|
1713
|
+
}
|
|
1714
|
+
const scope = portalRequest.scope;
|
|
1715
|
+
if (!scope) {
|
|
1716
|
+
ctx.throw(400, "Invalid portal scope");
|
|
1717
|
+
return;
|
|
1718
|
+
}
|
|
1719
|
+
await assertDesktopRouteMutationTargetsBelongToScope(ctx, scope);
|
|
1720
|
+
await assertDesktopRouteParentsBelongToScope(ctx, scope);
|
|
1721
|
+
(_b = ctx.action) == null ? void 0 : _b.mergeParams(
|
|
1722
|
+
{
|
|
1723
|
+
values: withoutDesktopRouteOwnerFields((_a = ctx.action) == null ? void 0 : _a.params.values)
|
|
1724
|
+
},
|
|
1725
|
+
{
|
|
1726
|
+
values: "overwrite"
|
|
1727
|
+
}
|
|
1728
|
+
);
|
|
1729
|
+
await next();
|
|
1730
|
+
}
|
|
1731
|
+
function getDesktopRoutePortalContext(context) {
|
|
1732
|
+
var _a;
|
|
1733
|
+
if (!isRecordLike(context)) {
|
|
1734
|
+
return;
|
|
1735
|
+
}
|
|
1736
|
+
const ctx = context;
|
|
1737
|
+
if (((_a = ctx.action) == null ? void 0 : _a.resourceName) !== "desktopRoutes" || !hasActionParam(ctx.action.params, "portal")) {
|
|
1738
|
+
return;
|
|
1739
|
+
}
|
|
1740
|
+
return ctx;
|
|
1741
|
+
}
|
|
1742
|
+
async function resolveRequiredDesktopRouteScope(ctx, transaction) {
|
|
1743
|
+
const portalRequest = await findRequestedMultiPortal(ctx, transaction);
|
|
1744
|
+
if (!portalRequest.scope) {
|
|
1745
|
+
ctx.throw(400, "Invalid portal scope");
|
|
1746
|
+
}
|
|
1747
|
+
return portalRequest.scope;
|
|
1748
|
+
}
|
|
1749
|
+
function withDesktopRouteScopeFilter(filter, scope) {
|
|
1750
|
+
return {
|
|
1751
|
+
...isRecordLike(filter) ? filter : {},
|
|
1752
|
+
...scope.filter
|
|
1753
|
+
};
|
|
1754
|
+
}
|
|
1755
|
+
class MultiPortalDesktopRouteRepository extends import_database.Repository {
|
|
1756
|
+
async withAuthoritativeScopeTransaction(existingTransaction, callback) {
|
|
1757
|
+
if (existingTransaction) {
|
|
1758
|
+
return callback(existingTransaction);
|
|
1759
|
+
}
|
|
1760
|
+
return this.database.sequelize.transaction(callback);
|
|
1761
|
+
}
|
|
1762
|
+
async create(options) {
|
|
1763
|
+
const ctx = getDesktopRoutePortalContext(options.context);
|
|
1764
|
+
if (!ctx) {
|
|
1765
|
+
return super.create(options);
|
|
1766
|
+
}
|
|
1767
|
+
return this.withAuthoritativeScopeTransaction(options.transaction, async (transaction) => {
|
|
1768
|
+
const scope = await resolveRequiredDesktopRouteScope(ctx, transaction);
|
|
1769
|
+
const values = withDesktopRouteEffectiveOwner(options.values, scope);
|
|
1770
|
+
await assertDesktopRouteParentsBelongToScope(ctx, scope, values, transaction);
|
|
1771
|
+
return super.create({
|
|
1772
|
+
...options,
|
|
1773
|
+
values,
|
|
1774
|
+
transaction
|
|
1775
|
+
});
|
|
1776
|
+
});
|
|
1777
|
+
}
|
|
1778
|
+
async update(options) {
|
|
1779
|
+
const ctx = getDesktopRoutePortalContext(options.context);
|
|
1780
|
+
if (!ctx) {
|
|
1781
|
+
return super.update(options);
|
|
1782
|
+
}
|
|
1783
|
+
return this.withAuthoritativeScopeTransaction(options.transaction, async (transaction) => {
|
|
1784
|
+
const scope = await resolveRequiredDesktopRouteScope(ctx, transaction);
|
|
1785
|
+
await assertDesktopRouteMutationTargetsBelongToScope(ctx, scope, transaction, options);
|
|
1786
|
+
const values = withoutDesktopRouteOwnerFields(options.values);
|
|
1787
|
+
await assertDesktopRouteParentsBelongToScope(ctx, scope, values, transaction);
|
|
1788
|
+
return super.update({
|
|
1789
|
+
...options,
|
|
1790
|
+
filter: withDesktopRouteScopeFilter(options.filter, scope),
|
|
1791
|
+
values,
|
|
1792
|
+
transaction
|
|
1793
|
+
});
|
|
1794
|
+
});
|
|
1795
|
+
}
|
|
1796
|
+
async updateOrCreate(options) {
|
|
1797
|
+
const ctx = getDesktopRoutePortalContext(options.context);
|
|
1798
|
+
if (!ctx) {
|
|
1799
|
+
return super.updateOrCreate(options);
|
|
1800
|
+
}
|
|
1801
|
+
return this.withAuthoritativeScopeTransaction(options.transaction, async (transaction) => {
|
|
1802
|
+
const scope = await resolveRequiredDesktopRouteScope(ctx, transaction);
|
|
1803
|
+
const values = withDesktopRouteEffectiveOwner(options.values, scope);
|
|
1804
|
+
await assertDesktopRouteParentsBelongToScope(ctx, scope, values, transaction);
|
|
1805
|
+
await assertDesktopRouteUpsertMatchesBelongToScope(ctx, scope, values, options.filterKeys, transaction);
|
|
1806
|
+
return super.updateOrCreate({
|
|
1807
|
+
...options,
|
|
1808
|
+
values,
|
|
1809
|
+
transaction
|
|
1810
|
+
});
|
|
1811
|
+
});
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
async function guardDesktopRouteMoveScope(ctx, next) {
|
|
1815
|
+
var _a;
|
|
1816
|
+
const portalRequest = await findRequestedMultiPortal(ctx);
|
|
1817
|
+
if (!portalRequest.requested) {
|
|
1818
|
+
await next();
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
const params = ((_a = ctx.action) == null ? void 0 : _a.params) ?? {};
|
|
1822
|
+
const sourceId = params.sourceId;
|
|
1823
|
+
const targetId = params.targetId;
|
|
1824
|
+
const hasTargetId = targetId !== void 0 && targetId !== null;
|
|
1825
|
+
if (typeof sourceId !== "string" && typeof sourceId !== "number" || hasTargetId && typeof targetId !== "string" && typeof targetId !== "number" || params.sortField !== void 0 && params.sortField !== "sort") {
|
|
1826
|
+
ctx.throw(400, "Invalid desktop route move scope");
|
|
1827
|
+
return;
|
|
1828
|
+
}
|
|
1829
|
+
await ctx.db.sequelize.transaction(async (transaction) => {
|
|
1830
|
+
const scope = await resolveRequiredDesktopRouteScope(ctx, transaction);
|
|
1831
|
+
const routeIds = uniqueDesktopRouteIds(hasTargetId ? [sourceId, targetId] : [sourceId]);
|
|
1832
|
+
const allRoutes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1833
|
+
fields: ["id", "parentId", "sort"],
|
|
1834
|
+
filter: {
|
|
1835
|
+
id: routeIds
|
|
1836
|
+
},
|
|
1837
|
+
lock: transaction.LOCK.UPDATE,
|
|
1838
|
+
transaction
|
|
1839
|
+
});
|
|
1840
|
+
const scopedRoutes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1841
|
+
fields: ["id", "parentId", "sort"],
|
|
1842
|
+
filter: {
|
|
1843
|
+
id: routeIds,
|
|
1844
|
+
...scope.filter
|
|
1845
|
+
},
|
|
1846
|
+
lock: transaction.LOCK.UPDATE,
|
|
1847
|
+
transaction
|
|
1848
|
+
});
|
|
1849
|
+
if (normalizeDesktopRouteTargetIds(allRoutes).join(",") !== normalizeDesktopRouteTargetIds(scopedRoutes).join(",") || scopedRoutes.length !== routeIds.length) {
|
|
1850
|
+
ctx.throw(400, "Move target does not belong to the requested portal scope");
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
const routesById = new Map(
|
|
1854
|
+
scopedRoutes.map((route) => [String(route.get("id")), route])
|
|
1855
|
+
);
|
|
1856
|
+
let source = routesById.get(String(sourceId));
|
|
1857
|
+
const target = hasTargetId ? routesById.get(String(targetId)) : void 0;
|
|
1858
|
+
if (!source || hasTargetId && !target) {
|
|
1859
|
+
ctx.throw(400, "Move target does not belong to the requested portal scope");
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
const targetScope = isRecordLike(params.targetScope) ? params.targetScope : void 0;
|
|
1863
|
+
const requestedParentId = targetScope == null ? void 0 : targetScope.parentId;
|
|
1864
|
+
const parentIds = uniqueDesktopRouteIds(
|
|
1865
|
+
[source.get("parentId"), target == null ? void 0 : target.get("parentId"), requestedParentId].filter(
|
|
1866
|
+
(id) => typeof id === "string" || typeof id === "number"
|
|
1867
|
+
)
|
|
1868
|
+
);
|
|
1869
|
+
if (parentIds.length) {
|
|
1870
|
+
const parents = await ctx.db.getRepository("desktopRoutes").find({
|
|
1871
|
+
fields: ["id"],
|
|
1872
|
+
filter: {
|
|
1873
|
+
id: parentIds,
|
|
1874
|
+
...scope.filter
|
|
1875
|
+
},
|
|
1876
|
+
lock: transaction.LOCK.UPDATE,
|
|
1877
|
+
transaction
|
|
1878
|
+
});
|
|
1879
|
+
if (normalizeDesktopRouteTargetIds(parents).join(",") !== parentIds.map(String).sort().join(",")) {
|
|
1880
|
+
ctx.throw(400, "Move parent does not belong to the requested portal scope");
|
|
1881
|
+
return;
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1884
|
+
const updateSource = async (values, silent) => {
|
|
1885
|
+
const [updatedSource] = await ctx.db.getRepository("desktopRoutes").update({
|
|
1886
|
+
filterByTk: sourceId,
|
|
1887
|
+
values,
|
|
1888
|
+
silent,
|
|
1889
|
+
transaction
|
|
1890
|
+
});
|
|
1891
|
+
if (!updatedSource) {
|
|
1892
|
+
ctx.throw(400, "Move source does not belong to the requested portal scope");
|
|
1893
|
+
}
|
|
1894
|
+
source = updatedSource;
|
|
1895
|
+
};
|
|
1896
|
+
if (target) {
|
|
1897
|
+
const targetParentId = target.get("parentId");
|
|
1898
|
+
if (source.get("parentId") !== targetParentId) {
|
|
1899
|
+
await updateSource({ parentId: targetParentId }, false);
|
|
1900
|
+
}
|
|
1901
|
+
const sourceSort = source.get("sort");
|
|
1902
|
+
let targetSort = target.get("sort");
|
|
1903
|
+
if (typeof sourceSort !== "number" || typeof targetSort !== "number") {
|
|
1904
|
+
ctx.throw(400, "Invalid desktop route sort value");
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
if (params.method === "insertAfter") {
|
|
1908
|
+
targetSort += 1;
|
|
1909
|
+
}
|
|
1910
|
+
const movesForward = targetSort > sourceSort;
|
|
1911
|
+
const affectedRoutes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1912
|
+
fields: ["id"],
|
|
1913
|
+
filter: {
|
|
1914
|
+
...scope.filter,
|
|
1915
|
+
parentId: targetParentId ?? null,
|
|
1916
|
+
sort: movesForward ? {
|
|
1917
|
+
$gt: sourceSort,
|
|
1918
|
+
$lte: targetSort
|
|
1919
|
+
} : {
|
|
1920
|
+
$lt: sourceSort,
|
|
1921
|
+
$gte: targetSort
|
|
1922
|
+
}
|
|
1923
|
+
},
|
|
1924
|
+
lock: transaction.LOCK.UPDATE,
|
|
1925
|
+
transaction
|
|
1926
|
+
});
|
|
1927
|
+
const affectedRouteIds = affectedRoutes.map((route) => route.get("id"));
|
|
1928
|
+
if (affectedRouteIds.length) {
|
|
1929
|
+
await ctx.db.getCollection("desktopRoutes").model.increment("sort", {
|
|
1930
|
+
where: {
|
|
1931
|
+
id: {
|
|
1932
|
+
[import_database.Op.in]: affectedRouteIds
|
|
1933
|
+
}
|
|
1934
|
+
},
|
|
1935
|
+
by: movesForward ? -1 : 1,
|
|
1936
|
+
silent: true,
|
|
1937
|
+
transaction
|
|
1938
|
+
});
|
|
1939
|
+
}
|
|
1940
|
+
await updateSource({ sort: targetSort }, true);
|
|
1941
|
+
}
|
|
1942
|
+
if (requestedParentId && source.get("parentId") !== requestedParentId) {
|
|
1943
|
+
await updateSource({ parentId: requestedParentId }, false);
|
|
1944
|
+
if (params.method === "prepend") {
|
|
1945
|
+
await updateSource({ sort: 0 }, true);
|
|
1946
|
+
}
|
|
1947
|
+
}
|
|
1948
|
+
if (params.sticky) {
|
|
1949
|
+
await updateSource({ sort: 0 }, true);
|
|
1950
|
+
}
|
|
1951
|
+
});
|
|
1952
|
+
ctx.body = "ok";
|
|
1953
|
+
}
|
|
1954
|
+
function normalizeDesktopRouteFilterByTk(filterByTk) {
|
|
1955
|
+
const values = Array.isArray(filterByTk) ? filterByTk : [filterByTk];
|
|
1956
|
+
return uniqueDesktopRouteIds(
|
|
1957
|
+
values.filter((value) => typeof value === "string" || typeof value === "number")
|
|
1958
|
+
);
|
|
1959
|
+
}
|
|
1960
|
+
async function getDesktopRouteDestroyOwnerScope(ctx, transaction) {
|
|
1961
|
+
var _a, _b, _c, _d;
|
|
1962
|
+
const hasPortalScope = hasActionParam((_a = ctx.action) == null ? void 0 : _a.params, "portal");
|
|
1963
|
+
const hasLayoutScope = hasActionParam((_b = ctx.action) == null ? void 0 : _b.params, "layout");
|
|
1964
|
+
const portalUid = getExplicitRequestedLayoutUid((_c = ctx.action) == null ? void 0 : _c.params.portal);
|
|
1965
|
+
const layoutUid = getExplicitRequestedLayoutUid((_d = ctx.action) == null ? void 0 : _d.params.layout);
|
|
1966
|
+
if (hasPortalScope && hasLayoutScope) {
|
|
1967
|
+
ctx.throw(400, "layout and portal cannot be used together");
|
|
1968
|
+
return;
|
|
1969
|
+
}
|
|
1970
|
+
if (hasPortalScope) {
|
|
1971
|
+
if (!portalUid) {
|
|
1972
|
+
ctx.throw(400, "Invalid portal scope");
|
|
1973
|
+
return;
|
|
1974
|
+
}
|
|
1975
|
+
const portalRequest = await findRequestedMultiPortal(ctx, transaction);
|
|
1976
|
+
if (!portalRequest.scope) {
|
|
1977
|
+
ctx.throw(400, "Invalid portal scope");
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
return portalRequest.scope;
|
|
1981
|
+
}
|
|
1982
|
+
if (!hasLayoutScope) {
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
if (!layoutUid) {
|
|
1986
|
+
ctx.throw(400, "Invalid layout scope");
|
|
1987
|
+
return;
|
|
1988
|
+
}
|
|
1989
|
+
const layout = await ctx.db.getRepository("uiLayouts").findOne({
|
|
1990
|
+
fields: ["uid"],
|
|
1991
|
+
filter: {
|
|
1992
|
+
uid: layoutUid
|
|
1993
|
+
},
|
|
1994
|
+
transaction
|
|
1995
|
+
});
|
|
1996
|
+
if (!layout) {
|
|
1997
|
+
ctx.throw(400, "Invalid layout scope");
|
|
1998
|
+
return;
|
|
1999
|
+
}
|
|
2000
|
+
return {
|
|
2001
|
+
filter: {
|
|
2002
|
+
"uiLayouts.uid": layoutUid
|
|
2003
|
+
},
|
|
2004
|
+
relation: "uiLayouts",
|
|
2005
|
+
uid: layoutUid
|
|
2006
|
+
};
|
|
2007
|
+
}
|
|
2008
|
+
async function findDesktopRouteDestroyRoots(ctx, transaction, scopeFilter) {
|
|
2009
|
+
var _a, _b, _c;
|
|
2010
|
+
const filterByTk = normalizeDesktopRouteFilterByTk((_a = ctx.action) == null ? void 0 : _a.params.filterByTk);
|
|
2011
|
+
const actionFilter = isRecordLike((_b = ctx.action) == null ? void 0 : _b.params.filter) ? (_c = ctx.action) == null ? void 0 : _c.params.filter : void 0;
|
|
2012
|
+
if (!filterByTk.length && !actionFilter) {
|
|
2013
|
+
return [];
|
|
2014
|
+
}
|
|
2015
|
+
const records = await ctx.db.getRepository("desktopRoutes").find({
|
|
2016
|
+
fields: ["id"],
|
|
2017
|
+
filter: {
|
|
2018
|
+
...actionFilter ?? {},
|
|
2019
|
+
...scopeFilter ?? {},
|
|
2020
|
+
...filterByTk.length ? { id: filterByTk } : {}
|
|
2021
|
+
},
|
|
2022
|
+
lock: transaction.LOCK.UPDATE,
|
|
2023
|
+
transaction
|
|
2024
|
+
});
|
|
2025
|
+
return collectDesktopRouteIds(records);
|
|
2026
|
+
}
|
|
2027
|
+
async function findDesktopRouteSubtreeIds(ctx, rootId, transaction) {
|
|
2028
|
+
const rootRoutes = await ctx.db.getRepository("desktopRoutes").find({
|
|
2029
|
+
fields: ["id"],
|
|
2030
|
+
filter: {
|
|
2031
|
+
id: rootId
|
|
2032
|
+
},
|
|
2033
|
+
lock: transaction.LOCK.UPDATE,
|
|
2034
|
+
transaction
|
|
2035
|
+
});
|
|
2036
|
+
const routeIds = collectDesktopRouteIds(rootRoutes);
|
|
2037
|
+
let parentIds = [...routeIds];
|
|
2038
|
+
while (parentIds.length) {
|
|
2039
|
+
const children = await ctx.db.getRepository("desktopRoutes").find({
|
|
2040
|
+
fields: ["id"],
|
|
2041
|
+
filter: {
|
|
2042
|
+
parentId: parentIds
|
|
2043
|
+
},
|
|
2044
|
+
lock: transaction.LOCK.UPDATE,
|
|
2045
|
+
sort: ["id"],
|
|
2046
|
+
transaction
|
|
2047
|
+
});
|
|
2048
|
+
const childIds = collectDesktopRouteIds(children).filter((routeId) => !routeIds.includes(routeId));
|
|
2049
|
+
routeIds.push(...childIds);
|
|
2050
|
+
parentIds = childIds;
|
|
2051
|
+
}
|
|
2052
|
+
return routeIds;
|
|
2053
|
+
}
|
|
2054
|
+
function getDesktopRouteOwnerUids(route, relation) {
|
|
2055
|
+
const owners = getRecordField(route, relation);
|
|
2056
|
+
if (!Array.isArray(owners)) {
|
|
2057
|
+
return [];
|
|
2058
|
+
}
|
|
2059
|
+
return owners.map((owner) => getRecordField(owner, "uid")).filter((uid) => typeof uid === "string" && !!uid);
|
|
2060
|
+
}
|
|
2061
|
+
function desktopRouteHasOwnerOutsideScope(route, scope) {
|
|
2062
|
+
const multiPortalUids = getDesktopRouteOwnerUids(route, "multiPortals");
|
|
2063
|
+
const uiLayoutUids = getDesktopRouteOwnerUids(route, "uiLayouts");
|
|
2064
|
+
if (scope.relation === "multiPortals") {
|
|
2065
|
+
return multiPortalUids.some((uid) => uid !== scope.uid) || uiLayoutUids.length > 0;
|
|
2066
|
+
}
|
|
2067
|
+
return uiLayoutUids.some((uid) => uid !== scope.uid) || multiPortalUids.length > 0;
|
|
2068
|
+
}
|
|
2069
|
+
async function detachDesktopRouteTreeOwner(ctx, routeIds, scope, transaction) {
|
|
2070
|
+
if (scope.relation === "multiPortals") {
|
|
2071
|
+
await ctx.db.getRepository("rolesMultiPortalDesktopRoutes").destroy({
|
|
2072
|
+
filter: {
|
|
2073
|
+
multiPortalUid: scope.uid,
|
|
2074
|
+
desktopRouteId: routeIds
|
|
2075
|
+
},
|
|
2076
|
+
transaction
|
|
2077
|
+
});
|
|
2078
|
+
}
|
|
2079
|
+
for (const routeId of routeIds) {
|
|
2080
|
+
await ctx.db.getRepository(`desktopRoutes.${scope.relation}`, routeId).remove({
|
|
2081
|
+
tk: scope.uid,
|
|
2082
|
+
transaction
|
|
2083
|
+
});
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
2086
|
+
async function detachOwnerAndFindDestroyRoots(ctx, routeIds, scope, transaction) {
|
|
2087
|
+
var _a;
|
|
2088
|
+
const routes = await ctx.db.getRepository("desktopRoutes").find({
|
|
2089
|
+
fields: ["id", "parentId"],
|
|
2090
|
+
appends: ["multiPortals", "uiLayouts"],
|
|
2091
|
+
filter: {
|
|
2092
|
+
id: routeIds
|
|
2093
|
+
},
|
|
2094
|
+
lock: transaction.LOCK.UPDATE,
|
|
2095
|
+
transaction
|
|
2096
|
+
});
|
|
2097
|
+
const routesById = new Map(routes.map((route) => [String(route.get("id")), route]));
|
|
2098
|
+
const ownerlessRouteIds = new Set(
|
|
2099
|
+
routes.filter((route) => !desktopRouteHasOwnerOutsideScope(route, scope)).map((route) => String(route.get("id")))
|
|
2100
|
+
);
|
|
2101
|
+
await detachDesktopRouteTreeOwner(ctx, routeIds, scope, transaction);
|
|
2102
|
+
for (const route of routes) {
|
|
2103
|
+
if (ownerlessRouteIds.has(String(route.get("id")))) {
|
|
2104
|
+
continue;
|
|
2105
|
+
}
|
|
2106
|
+
let parentId = route.get("parentId");
|
|
2107
|
+
const visitedParentIds = /* @__PURE__ */ new Set();
|
|
2108
|
+
while (parentId !== null && parentId !== void 0 && ownerlessRouteIds.has(String(parentId))) {
|
|
2109
|
+
const normalizedParentId = String(parentId);
|
|
2110
|
+
if (visitedParentIds.has(normalizedParentId)) {
|
|
2111
|
+
ctx.throw(400, "Invalid desktop route tree");
|
|
2112
|
+
return [];
|
|
2113
|
+
}
|
|
2114
|
+
visitedParentIds.add(normalizedParentId);
|
|
2115
|
+
parentId = ((_a = routesById.get(normalizedParentId)) == null ? void 0 : _a.get("parentId")) ?? null;
|
|
2116
|
+
}
|
|
2117
|
+
if (route.get("parentId") !== parentId) {
|
|
2118
|
+
await ctx.db.getRepository("desktopRoutes").update({
|
|
2119
|
+
filterByTk: route.get("id"),
|
|
2120
|
+
values: {
|
|
2121
|
+
parentId
|
|
2122
|
+
},
|
|
2123
|
+
transaction
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
2127
|
+
return routes.filter((route) => {
|
|
2128
|
+
if (!ownerlessRouteIds.has(String(route.get("id")))) {
|
|
2129
|
+
return false;
|
|
2130
|
+
}
|
|
2131
|
+
const parentId = route.get("parentId");
|
|
2132
|
+
return parentId === null || parentId === void 0 || !ownerlessRouteIds.has(String(parentId));
|
|
2133
|
+
}).map((route) => route.get("id"));
|
|
2134
|
+
}
|
|
2135
|
+
async function destroyScopedDesktopRoutes(ctx, next) {
|
|
2136
|
+
var _a, _b;
|
|
2137
|
+
if (!hasActionParam((_a = ctx.action) == null ? void 0 : _a.params, "portal") && !hasActionParam((_b = ctx.action) == null ? void 0 : _b.params, "layout")) {
|
|
2138
|
+
await next();
|
|
2139
|
+
return;
|
|
2140
|
+
}
|
|
2141
|
+
const result = await ctx.db.sequelize.transaction(async (transaction) => {
|
|
2142
|
+
const scope = await getDesktopRouteDestroyOwnerScope(ctx, transaction);
|
|
2143
|
+
if (!scope) {
|
|
2144
|
+
return true;
|
|
2145
|
+
}
|
|
2146
|
+
const allRootIds = (await findDesktopRouteDestroyRoots(ctx, transaction)).sort(
|
|
2147
|
+
(left, right) => String(left).localeCompare(String(right))
|
|
2148
|
+
);
|
|
2149
|
+
const rootIds = (await findDesktopRouteDestroyRoots(ctx, transaction, scope.filter)).sort(
|
|
2150
|
+
(left, right) => String(left).localeCompare(String(right))
|
|
2151
|
+
);
|
|
2152
|
+
if (allRootIds.map(String).join(",") !== rootIds.map(String).join(",")) {
|
|
2153
|
+
ctx.throw(400, "Desktop route does not belong to the requested portal scope");
|
|
2154
|
+
return true;
|
|
2155
|
+
}
|
|
2156
|
+
const routeTrees = [];
|
|
2157
|
+
for (const rootId of rootIds) {
|
|
2158
|
+
routeTrees.push({
|
|
2159
|
+
rootId,
|
|
2160
|
+
routeIds: await findDesktopRouteSubtreeIds(ctx, rootId, transaction)
|
|
2161
|
+
});
|
|
2162
|
+
}
|
|
2163
|
+
const rootRouteTrees = routeTrees.filter(
|
|
2164
|
+
(routeTree, index) => !routeTrees.some(
|
|
2165
|
+
(candidate, candidateIndex) => candidateIndex !== index && candidate.routeIds.includes(routeTree.rootId)
|
|
2166
|
+
)
|
|
2167
|
+
);
|
|
2168
|
+
const physicalDestroyRootIds = [];
|
|
2169
|
+
for (const routeTree of rootRouteTrees) {
|
|
2170
|
+
physicalDestroyRootIds.push(
|
|
2171
|
+
...await detachOwnerAndFindDestroyRoots(ctx, routeTree.routeIds, scope, transaction)
|
|
2172
|
+
);
|
|
2173
|
+
}
|
|
2174
|
+
if (!physicalDestroyRootIds.length) {
|
|
2175
|
+
return true;
|
|
2176
|
+
}
|
|
2177
|
+
return ctx.db.getRepository("desktopRoutes").destroy({
|
|
2178
|
+
context: ctx,
|
|
2179
|
+
filterByTk: physicalDestroyRootIds.length === 1 ? physicalDestroyRootIds[0] : physicalDestroyRootIds,
|
|
2180
|
+
transaction
|
|
2181
|
+
});
|
|
2182
|
+
});
|
|
2183
|
+
ctx.status = 200;
|
|
2184
|
+
ctx.body = result;
|
|
2185
|
+
}
|
|
1398
2186
|
async function listEnabledMultiPortals(ctx, next) {
|
|
1399
2187
|
const records = await ctx.db.getRepository("multiPortals").find({
|
|
1400
|
-
filter:
|
|
2188
|
+
filter: {
|
|
1401
2189
|
enabled: true,
|
|
1402
2190
|
"uiLayout.enabled": true
|
|
1403
|
-
}
|
|
2191
|
+
},
|
|
1404
2192
|
fields: [...MULTI_PORTAL_RUNTIME_QUERY_FIELDS],
|
|
1405
2193
|
appends: ["uiLayout"],
|
|
1406
2194
|
sort: ["uid"]
|
|
@@ -1410,40 +2198,33 @@ async function listEnabledMultiPortals(ctx, next) {
|
|
|
1410
2198
|
}
|
|
1411
2199
|
async function listAccessibleMultiPortals(ctx, next) {
|
|
1412
2200
|
const accessiblePortalUids = await listCurrentRoleAccessibleMultiPortalUids(ctx);
|
|
1413
|
-
if (Array.isArray(accessiblePortalUids) && accessiblePortalUids.length === 0) {
|
|
1414
|
-
ctx.body = [];
|
|
1415
|
-
await next();
|
|
1416
|
-
return;
|
|
1417
|
-
}
|
|
1418
|
-
const filter = {
|
|
1419
|
-
enabled: true,
|
|
1420
|
-
"uiLayout.enabled": true
|
|
1421
|
-
};
|
|
1422
|
-
if (Array.isArray(accessiblePortalUids)) {
|
|
1423
|
-
filter.uid = accessiblePortalUids;
|
|
1424
|
-
}
|
|
1425
2201
|
const records = await ctx.db.getRepository("multiPortals").find({
|
|
1426
|
-
filter:
|
|
2202
|
+
filter: {
|
|
2203
|
+
enabled: true,
|
|
2204
|
+
"uiLayout.enabled": true
|
|
2205
|
+
},
|
|
1427
2206
|
fields: [...MULTI_PORTAL_ACCESSIBLE_QUERY_FIELDS],
|
|
1428
2207
|
appends: ["uiLayout"],
|
|
1429
2208
|
sort: ["uid"]
|
|
1430
2209
|
});
|
|
1431
|
-
|
|
2210
|
+
const accessiblePortalUidSet = Array.isArray(accessiblePortalUids) ? new Set(accessiblePortalUids) : void 0;
|
|
2211
|
+
const accessibleRecords = records.filter(
|
|
2212
|
+
(record) => (0, import_constants.isDefaultLayoutMultiPortalUid)(record.get("uid")) || !accessiblePortalUidSet || accessiblePortalUidSet.has(String(record.get("uid")))
|
|
2213
|
+
);
|
|
2214
|
+
ctx.body = accessibleRecords.map((record) => pickMultiPortalAccessibleFields(record));
|
|
1432
2215
|
await next();
|
|
1433
2216
|
}
|
|
1434
|
-
async function grantDefaultRouteAccessToNewMultiPortalRoutes(ctx, multiPortalUid, desktopRouteIds) {
|
|
2217
|
+
async function grantDefaultRouteAccessToNewMultiPortalRoutes(ctx, multiPortalUid, desktopRouteIds, transaction) {
|
|
1435
2218
|
if (!desktopRouteIds.length) {
|
|
1436
2219
|
return;
|
|
1437
2220
|
}
|
|
1438
|
-
if (!ctx.db.getCollection("rolesMultiPortalRoutePolicies") || !ctx.db.getCollection("rolesMultiPortalDesktopRoutes")) {
|
|
1439
|
-
return;
|
|
1440
|
-
}
|
|
1441
2221
|
const routePolicies = await ctx.db.getRepository("rolesMultiPortalRoutePolicies").find({
|
|
1442
2222
|
fields: ["roleName"],
|
|
1443
2223
|
filter: {
|
|
1444
2224
|
multiPortalUid,
|
|
1445
2225
|
allowNewMenu: true
|
|
1446
|
-
}
|
|
2226
|
+
},
|
|
2227
|
+
transaction
|
|
1447
2228
|
});
|
|
1448
2229
|
const roleNames = routePolicies.map((routePolicy) => routePolicy.get("roleName")).filter((roleName) => typeof roleName === "string" && !!roleName);
|
|
1449
2230
|
if (!roleNames.length) {
|
|
@@ -1459,26 +2240,52 @@ async function grantDefaultRouteAccessToNewMultiPortalRoutes(ctx, multiPortalUid
|
|
|
1459
2240
|
multiPortalUid,
|
|
1460
2241
|
desktopRouteId
|
|
1461
2242
|
},
|
|
1462
|
-
context: ctx
|
|
2243
|
+
context: ctx,
|
|
2244
|
+
transaction
|
|
1463
2245
|
});
|
|
1464
2246
|
}
|
|
1465
2247
|
}
|
|
1466
2248
|
}
|
|
1467
|
-
async function
|
|
1468
|
-
if (!desktopRouteIds.length
|
|
1469
|
-
return;
|
|
1470
|
-
}
|
|
1471
|
-
const portalOnlyRouteIds = await getPortalOnlyDesktopRouteIds(ctx, desktopRouteIds);
|
|
1472
|
-
if (portalOnlyRouteIds.size === 0) {
|
|
2249
|
+
async function removeLayoutRoutePermissionsFromPortalRoutes(ctx, desktopRouteIds, transaction) {
|
|
2250
|
+
if (!desktopRouteIds.length) {
|
|
1473
2251
|
return;
|
|
1474
2252
|
}
|
|
1475
2253
|
await ctx.db.getRepository("rolesDesktopRoutes").destroy({
|
|
1476
2254
|
filter: {
|
|
1477
|
-
desktopRouteId:
|
|
2255
|
+
desktopRouteId: desktopRouteIds
|
|
1478
2256
|
},
|
|
1479
|
-
context: ctx
|
|
2257
|
+
context: ctx,
|
|
2258
|
+
transaction
|
|
1480
2259
|
});
|
|
1481
2260
|
}
|
|
2261
|
+
async function reconcileCreatedDesktopRoutePermissions(desktopRoute, options) {
|
|
2262
|
+
const ctx = options == null ? void 0 : options.context;
|
|
2263
|
+
if (!ctx) {
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
const portalRequest = await findRequestedMultiPortal(ctx, options.transaction);
|
|
2267
|
+
if (!portalRequest.requested) {
|
|
2268
|
+
return;
|
|
2269
|
+
}
|
|
2270
|
+
const scope = portalRequest.scope;
|
|
2271
|
+
if (!scope) {
|
|
2272
|
+
ctx.throw(400, "Invalid portal scope");
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2275
|
+
if (scope.relation !== "multiPortals") {
|
|
2276
|
+
return;
|
|
2277
|
+
}
|
|
2278
|
+
const transaction = options.transaction;
|
|
2279
|
+
if (!transaction) {
|
|
2280
|
+
throw new Error("Portal desktop route creation requires a transaction");
|
|
2281
|
+
}
|
|
2282
|
+
const desktopRouteId = desktopRoute.get("id");
|
|
2283
|
+
if (typeof desktopRouteId !== "string" && typeof desktopRouteId !== "number") {
|
|
2284
|
+
throw new Error("Created Portal desktop route has no identifier");
|
|
2285
|
+
}
|
|
2286
|
+
await removeLayoutRoutePermissionsFromPortalRoutes(ctx, [desktopRouteId], transaction);
|
|
2287
|
+
await grantDefaultRouteAccessToNewMultiPortalRoutes(ctx, scope.portalUid, [desktopRouteId], transaction);
|
|
2288
|
+
}
|
|
1482
2289
|
async function grantDefaultAccessToNewMultiPortal(db, multiPortal, options) {
|
|
1483
2290
|
if (multiPortal.get("enabled") !== true) {
|
|
1484
2291
|
return;
|
|
@@ -1487,7 +2294,7 @@ async function grantDefaultAccessToNewMultiPortal(db, multiPortal, options) {
|
|
|
1487
2294
|
if (typeof multiPortalUid !== "string" || !multiPortalUid) {
|
|
1488
2295
|
return;
|
|
1489
2296
|
}
|
|
1490
|
-
if (
|
|
2297
|
+
if ((0, import_constants.isDefaultLayoutMultiPortalUid)(multiPortalUid)) {
|
|
1491
2298
|
return;
|
|
1492
2299
|
}
|
|
1493
2300
|
if (!db.getCollection("roles") || !db.getCollection("rolesMultiPortals")) {
|
|
@@ -1521,7 +2328,7 @@ async function grantDefaultAccessToNewMultiPortal(db, multiPortal, options) {
|
|
|
1521
2328
|
transaction: options == null ? void 0 : options.transaction
|
|
1522
2329
|
});
|
|
1523
2330
|
}
|
|
1524
|
-
if (!db.getCollection("rolesMultiPortalRoutePolicies")) {
|
|
2331
|
+
if (multiPortal.get("portalType") === "ai" || !db.getCollection("rolesMultiPortalRoutePolicies")) {
|
|
1525
2332
|
return;
|
|
1526
2333
|
}
|
|
1527
2334
|
const routePolicyRepository = db.getRepository("rolesMultiPortalRoutePolicies");
|
|
@@ -1555,7 +2362,8 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1555
2362
|
return {
|
|
1556
2363
|
appName: this.getAppName(),
|
|
1557
2364
|
portalName,
|
|
1558
|
-
enabled: readField("enabled") === true
|
|
2365
|
+
enabled: readField("enabled") === true,
|
|
2366
|
+
config: getPortalStorageConfig(readField("options"))
|
|
1559
2367
|
};
|
|
1560
2368
|
}
|
|
1561
2369
|
warnPortalStorageSyncFailed(error) {
|
|
@@ -1620,6 +2428,8 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1620
2428
|
await copyPortalTemplate(template.dir, portalDir);
|
|
1621
2429
|
await appendPortalStorageLog(logPath, `Default portal template copied to ${portalDir}.`);
|
|
1622
2430
|
}
|
|
2431
|
+
await ensurePortalStorageConfigFiles(portalDir, item);
|
|
2432
|
+
await appendPortalStorageLog(logPath, `Portal configuration files updated in ${portalDir}.`);
|
|
1623
2433
|
if (item.enabled) {
|
|
1624
2434
|
await buildPortalStorageItem(portalDir, item);
|
|
1625
2435
|
return;
|
|
@@ -1649,6 +2459,7 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1649
2459
|
await this.schedulePortalTemplateCopyAndBuild(item, template, portalDir);
|
|
1650
2460
|
return;
|
|
1651
2461
|
}
|
|
2462
|
+
await ensurePortalStorageConfigFiles(portalDir, item);
|
|
1652
2463
|
if (item.enabled) {
|
|
1653
2464
|
if (!await pathExists(portalIndex)) {
|
|
1654
2465
|
await buildPortalStorageItem(portalDir, item);
|
|
@@ -1828,10 +2639,10 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1828
2639
|
}
|
|
1829
2640
|
async listAppPortalManifest(options) {
|
|
1830
2641
|
const records = await this.db.getRepository("multiPortals").find({
|
|
1831
|
-
filter:
|
|
2642
|
+
filter: {
|
|
1832
2643
|
enabled: true,
|
|
1833
2644
|
"uiLayout.enabled": true
|
|
1834
|
-
}
|
|
2645
|
+
},
|
|
1835
2646
|
fields: [...MULTI_PORTAL_ACCESSIBLE_QUERY_FIELDS],
|
|
1836
2647
|
appends: ["uiLayout"],
|
|
1837
2648
|
sort: ["uid"],
|
|
@@ -1867,7 +2678,7 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1867
2678
|
const portalType = getRecordField(multiPortal, "portalType");
|
|
1868
2679
|
const routePath = getRecordField(multiPortal, "routePath");
|
|
1869
2680
|
const enabled = getRecordField(multiPortal, "enabled");
|
|
1870
|
-
if (typeof uid !== "string" || !uid ||
|
|
2681
|
+
if (typeof uid !== "string" || !uid || typeof title !== "string" || typeof routePath !== "string" || !routePath || enabled !== true) {
|
|
1871
2682
|
return null;
|
|
1872
2683
|
}
|
|
1873
2684
|
const uiLayoutUid = getRecordField(multiPortal, "uiLayoutUid");
|
|
@@ -1939,9 +2750,9 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1939
2750
|
return;
|
|
1940
2751
|
}
|
|
1941
2752
|
const records = await this.db.getRepository("multiPortals").find({
|
|
1942
|
-
filter:
|
|
2753
|
+
filter: {
|
|
1943
2754
|
uiLayoutUid
|
|
1944
|
-
}
|
|
2755
|
+
},
|
|
1945
2756
|
fields: [...MULTI_PORTAL_ACCESSIBLE_QUERY_FIELDS],
|
|
1946
2757
|
transaction: options == null ? void 0 : options.transaction
|
|
1947
2758
|
});
|
|
@@ -2000,6 +2811,9 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2000
2811
|
}
|
|
2001
2812
|
}
|
|
2002
2813
|
async beforeLoad() {
|
|
2814
|
+
this.app.db.registerRepositories({ MultiPortalDesktopRouteRepository });
|
|
2815
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:list", addDesktopRouteEffectiveScopeFilter);
|
|
2816
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:get", addDesktopRouteEffectiveScopeFilter);
|
|
2003
2817
|
this.app.resourceManager.registerPreActionHandler(
|
|
2004
2818
|
"desktopRoutes:listAccessible",
|
|
2005
2819
|
addMultiPortalListAccessibleGuard
|
|
@@ -2012,22 +2826,23 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2012
2826
|
this.app.resourceManager.registerPreActionHandler("multiPortals:create", captureSkipCreatePortalDirectory);
|
|
2013
2827
|
this.app.resourceManager.registerPreActionHandler("multiPortals:create", normalizeMultiPortalSlugValues);
|
|
2014
2828
|
this.app.resourceManager.registerPreActionHandler("multiPortals:create", preventUiLayoutRouteNameConflict);
|
|
2015
|
-
this.app.resourceManager.registerPreActionHandler("multiPortals:update",
|
|
2829
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:update", preventMultiPortalBackingLayoutChange);
|
|
2016
2830
|
this.app.resourceManager.registerPreActionHandler("multiPortals:update", normalizeMultiPortalSlugValues);
|
|
2017
2831
|
this.app.resourceManager.registerPreActionHandler("multiPortals:update", preventUiLayoutRouteNameConflict);
|
|
2832
|
+
this.app.resourceManager.registerPreActionHandler(
|
|
2833
|
+
"multiPortals:firstOrCreate",
|
|
2834
|
+
preventMultiPortalBackingLayoutChange
|
|
2835
|
+
);
|
|
2018
2836
|
this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", captureSkipCreatePortalDirectory);
|
|
2019
2837
|
this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", normalizeMultiPortalSlugValues);
|
|
2020
2838
|
this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", preventUiLayoutRouteNameConflict);
|
|
2021
|
-
this.app.resourceManager.registerPreActionHandler("desktopRoutes:create", captureExplicitDesktopRouteUiLayouts, {
|
|
2022
|
-
before: UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG
|
|
2023
|
-
});
|
|
2024
2839
|
this.app.resourceManager.registerPreActionHandler(
|
|
2025
|
-
"
|
|
2026
|
-
|
|
2027
|
-
{
|
|
2028
|
-
before: UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG
|
|
2029
|
-
}
|
|
2840
|
+
"multiPortals:updateOrCreate",
|
|
2841
|
+
preventMultiPortalBackingLayoutChange
|
|
2030
2842
|
);
|
|
2843
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:updateOrCreate", captureSkipCreatePortalDirectory);
|
|
2844
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:updateOrCreate", normalizeMultiPortalSlugValues);
|
|
2845
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:updateOrCreate", preventUiLayoutRouteNameConflict);
|
|
2031
2846
|
this.app.resourceManager.registerPreActionHandler("desktopRoutes:create", addDesktopRouteCreateMultiPortal, {
|
|
2032
2847
|
after: UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG
|
|
2033
2848
|
});
|
|
@@ -2038,6 +2853,9 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2038
2853
|
after: UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG
|
|
2039
2854
|
}
|
|
2040
2855
|
);
|
|
2856
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:update", guardDesktopRouteUpdateScope);
|
|
2857
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:move", guardDesktopRouteMoveScope);
|
|
2858
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:destroy", destroyScopedDesktopRoutes);
|
|
2041
2859
|
}
|
|
2042
2860
|
async load() {
|
|
2043
2861
|
this.app.acl.registerSnippet({
|
|
@@ -2078,6 +2896,7 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2078
2896
|
this.app.db.on("roles.beforeCreate", (role) => {
|
|
2079
2897
|
(0, import_ensureDefaultRoleMultiPortalAccess.applyDefaultRoleMultiPortalAccess)(role);
|
|
2080
2898
|
});
|
|
2899
|
+
this.app.db.on("desktopRoutes.afterCreate", reconcileCreatedDesktopRoutePermissions);
|
|
2081
2900
|
this.app.db.on("multiPortals.afterCreate", async (multiPortal, options) => {
|
|
2082
2901
|
await grantDefaultAccessToNewMultiPortal(this.app.db, multiPortal, options);
|
|
2083
2902
|
await this.publishAppManifestItem(multiPortal, options);
|
|
@@ -2102,14 +2921,18 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2102
2921
|
});
|
|
2103
2922
|
}
|
|
2104
2923
|
async install() {
|
|
2105
|
-
await
|
|
2106
|
-
|
|
2924
|
+
const version = await this.app.version.get();
|
|
2925
|
+
if (!version) {
|
|
2926
|
+
await (0, import_ensureDefaultRoleMultiPortalAccess.ensureDefaultRoleMultiPortalAccess)(this.db);
|
|
2927
|
+
await seedFreshMultiPortals(this.db);
|
|
2928
|
+
} else {
|
|
2929
|
+
await seedHistoricalMultiPortals(this.db);
|
|
2930
|
+
}
|
|
2107
2931
|
await this.reconcilePortalStorage();
|
|
2108
2932
|
await this.reconcileAppManifest();
|
|
2109
2933
|
}
|
|
2110
2934
|
async afterEnable() {
|
|
2111
|
-
await
|
|
2112
|
-
await (0, import_ensureDefaultRoleMultiPortalAccess.ensureDefaultRoleMultiPortalAccess)(this.db);
|
|
2935
|
+
await repairFixedLayoutMultiPortalRecords(this.db);
|
|
2113
2936
|
await this.reconcilePortalStorage();
|
|
2114
2937
|
await this.reconcileAppManifest();
|
|
2115
2938
|
}
|