@nocobase/plugin-multi-portal 2.2.0-alpha.11 → 2.4.0-alpha.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/README.md +14 -6
- package/dist/client-v2/354.87f18462949b5c23.js +10 -0
- package/dist/client-v2/499.245037eea4cc7266.js +10 -0
- package/dist/client-v2/556.443e73c9ca4c18b9.js +10 -0
- package/dist/client-v2/778.affaa69b23857ef2.js +10 -0
- package/dist/client-v2/993.7a1cc7aa6cec0160.js +10 -0
- package/dist/client-v2/PortalAccessBoundary.d.ts +49 -0
- package/dist/client-v2/RootLanding.d.ts +18 -0
- package/dist/client-v2/index.js +1 -1
- package/dist/client-v2/interceptor.d.ts +13 -0
- package/dist/client-v2/layoutRegistration.d.ts +5 -7
- package/dist/client-v2/models/MultiPortalLayoutModels.d.ts +14 -0
- package/dist/client-v2/models/MultiPortalMobilePageModels.d.ts +5 -11
- package/dist/client-v2/pages/MultiPortalsPage.d.ts +13 -6
- package/dist/client-v2/pages/PortalRoutesDrawer.d.ts +13 -0
- package/dist/client-v2/permissions/MultiPortalPermissionsTab.d.ts +1 -0
- package/dist/client-v2/portalAccess.d.ts +73 -0
- package/dist/client-v2/routeRepositoryScope.d.ts +5 -2
- package/dist/client-v2/routeUrl.d.ts +1 -0
- package/dist/constants.d.ts +19 -0
- package/dist/constants.js +76 -0
- package/dist/externalVersion.js +11 -9
- package/dist/locale/en-US.json +93 -11
- package/dist/locale/zh-CN.json +113 -31
- package/dist/node_modules/tar/package.json +1 -1
- package/dist/server/collections/desktopRoutes.js +1 -0
- package/dist/server/collections/multiPortals.js +12 -11
- 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 +7 -5
- package/dist/server/plugin.js +1415 -502
- package/dist/swagger/index.d.ts +152 -0
- package/dist/swagger/index.js +142 -0
- package/package.json +6 -7
- package/dist/client-v2/354.ead3e96143164cd5.js +0 -10
- package/dist/client-v2/499.ef2ff0c022687485.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",
|
|
@@ -58,9 +60,9 @@ const MULTI_PORTAL_RUNTIME_FIELDS = [
|
|
|
58
60
|
"portalName",
|
|
59
61
|
"routePath",
|
|
60
62
|
"authCheck",
|
|
61
|
-
"enabled"
|
|
63
|
+
"enabled",
|
|
64
|
+
"uiLayoutUid"
|
|
62
65
|
];
|
|
63
|
-
const MULTI_PORTAL_RUNTIME_QUERY_FIELDS = [...MULTI_PORTAL_RUNTIME_FIELDS, "uiLayoutUid"];
|
|
64
66
|
const MULTI_PORTAL_ACCESSIBLE_FIELDS = [
|
|
65
67
|
"uid",
|
|
66
68
|
"title",
|
|
@@ -69,18 +71,15 @@ const MULTI_PORTAL_ACCESSIBLE_FIELDS = [
|
|
|
69
71
|
"portalName",
|
|
70
72
|
"routePath",
|
|
71
73
|
"authCheck",
|
|
72
|
-
"enabled"
|
|
74
|
+
"enabled",
|
|
75
|
+
"uiLayoutUid"
|
|
73
76
|
];
|
|
74
|
-
const MULTI_PORTAL_ACCESSIBLE_QUERY_FIELDS = [...MULTI_PORTAL_ACCESSIBLE_FIELDS, "uiLayoutUid"];
|
|
75
|
-
const MULTI_PORTAL_UI_LAYOUT_RUNTIME_FIELDS = ["layoutType"];
|
|
76
77
|
const DESKTOP_ROUTE_ROLE_PERMISSION_TARGET_FIELDS = ["id", "title", "hidden", "parentId", "options"];
|
|
77
78
|
const UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG = "plugin-ui-layout:desktop-route-write-layout";
|
|
78
79
|
const MAIN_APP_NAME = "main";
|
|
79
80
|
const UNION_ROLE_KEY = "__union__";
|
|
80
81
|
const MULTI_PORTAL_MANIFEST_NAMESPACE = "multi-portal";
|
|
81
82
|
const MULTI_PORTAL_MANIFEST_SYNC_MESSAGE_TYPE = "multi-portal:app-manifest-changed";
|
|
82
|
-
const DEFAULT_INIT_PORTAL_TYPE = "no-code";
|
|
83
|
-
const DEFAULT_INIT_PORTAL_NAME = "admin";
|
|
84
83
|
const DEFAULT_INIT_PORTAL_TEMPLATE = "@nocobase/portal-template-default";
|
|
85
84
|
const PORTAL_CLIENT_PREFIX = "x";
|
|
86
85
|
const PORTAL_DEPLOY_UPLOAD_LIMIT = 200 * 1024 * 1024;
|
|
@@ -90,9 +89,9 @@ const PORTAL_PUBLIC_FILE_MODE = 420;
|
|
|
90
89
|
const PORTAL_TEMPLATE_NPM_PACK_TIMEOUT_MS = 3e4;
|
|
91
90
|
const DEFAULT_MULTI_PORTAL_UID = "__default_portal__";
|
|
92
91
|
const MULTI_PORTAL_SLUG_PATTERN = /^[a-z0-9_-]+$/;
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
const
|
|
92
|
+
const PORTAL_ACCESS_DENIED_CODE = "PORTAL_ACCESS_DENIED";
|
|
93
|
+
const PORTAL_CONTEXT_INVALID_CODE = "PORTAL_CONTEXT_INVALID";
|
|
94
|
+
const PORTAL_NOT_FOUND_CODE = "PORTAL_NOT_FOUND";
|
|
96
95
|
const MULTI_PORTAL_MANAGEMENT_ACTIONS = [
|
|
97
96
|
"multiPortals:list",
|
|
98
97
|
"multiPortals:get",
|
|
@@ -100,17 +99,31 @@ const MULTI_PORTAL_MANAGEMENT_ACTIONS = [
|
|
|
100
99
|
"multiPortals:create",
|
|
101
100
|
"multiPortals:update",
|
|
102
101
|
"multiPortals:firstOrCreate",
|
|
102
|
+
"multiPortals:setDefault",
|
|
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
|
|
122
|
+
const MULTI_PORTAL_SEED_TYPES = ["no-code", "ai"];
|
|
123
|
+
const MULTI_PORTAL_SEED_TYPE_SET = new Set(MULTI_PORTAL_SEED_TYPES);
|
|
124
|
+
function isMultiPortalSeedType(value) {
|
|
125
|
+
return typeof value === "string" && MULTI_PORTAL_SEED_TYPE_SET.has(value);
|
|
126
|
+
}
|
|
114
127
|
const SKIP_CREATE_PORTAL_DIRECTORY = Symbol("skipCreatePortalDirectory");
|
|
115
128
|
function getRecordField(record, field) {
|
|
116
129
|
if (!record || typeof record !== "object") {
|
|
@@ -136,44 +149,58 @@ async function pathExists(filePath) {
|
|
|
136
149
|
function trimString(value) {
|
|
137
150
|
return String(value ?? "").trim();
|
|
138
151
|
}
|
|
139
|
-
function isInitPortalType(value) {
|
|
140
|
-
return INIT_PORTAL_TYPES.includes(value);
|
|
141
|
-
}
|
|
142
|
-
function getInitPortalType() {
|
|
143
|
-
const portalType = trimString(process.env.INIT_PORTAL_TYPE) || DEFAULT_INIT_PORTAL_TYPE;
|
|
144
|
-
if (!isInitPortalType(portalType)) {
|
|
145
|
-
throw new Error('INIT_PORTAL_TYPE must be either "no-code" or "ai".');
|
|
146
|
-
}
|
|
147
|
-
return portalType;
|
|
148
|
-
}
|
|
149
|
-
function getInitPortalName() {
|
|
150
|
-
const portalName = trimString(process.env.INIT_PORTAL_NAME) || DEFAULT_INIT_PORTAL_NAME;
|
|
151
|
-
if (!MULTI_PORTAL_SLUG_PATTERN.test(portalName)) {
|
|
152
|
-
throw new Error("INIT_PORTAL_NAME can only contain lowercase letters, numbers, hyphens, and underscores.");
|
|
153
|
-
}
|
|
154
|
-
return portalName;
|
|
155
|
-
}
|
|
156
152
|
function getInitPortalTemplate() {
|
|
157
153
|
return trimString(process.env.INIT_PORTAL_TEMPLATE) || DEFAULT_INIT_PORTAL_TEMPLATE;
|
|
158
154
|
}
|
|
159
|
-
function
|
|
160
|
-
|
|
161
|
-
return title || portalName;
|
|
155
|
+
function hasDeprecatedInitPortalEnv() {
|
|
156
|
+
return Boolean(trimString(process.env.INIT_PORTAL_TYPE) || trimString(process.env.INIT_PORTAL_NAME));
|
|
162
157
|
}
|
|
163
|
-
function
|
|
164
|
-
const portalName = getInitPortalName();
|
|
158
|
+
function getDefaultAiMultiPortalRecord(options = {}) {
|
|
165
159
|
return {
|
|
166
160
|
uid: DEFAULT_MULTI_PORTAL_UID,
|
|
167
|
-
title:
|
|
161
|
+
title: "Main",
|
|
168
162
|
icon: "DesktopOutlined",
|
|
169
|
-
portalType:
|
|
170
|
-
portalName,
|
|
171
|
-
routePath:
|
|
163
|
+
portalType: "ai",
|
|
164
|
+
portalName: "main",
|
|
165
|
+
routePath: "/main",
|
|
172
166
|
authCheck: true,
|
|
173
167
|
enabled: true,
|
|
174
|
-
|
|
168
|
+
...options.isDefault ? { isDefault: true } : {},
|
|
169
|
+
uiLayoutUid: import_constants.ADMIN_UI_LAYOUT_UID
|
|
175
170
|
};
|
|
176
171
|
}
|
|
172
|
+
function getFreshMultiPortalRecords() {
|
|
173
|
+
return [getDefaultAiMultiPortalRecord({ isDefault: true }), ...getFixedLayoutMultiPortalRecords()];
|
|
174
|
+
}
|
|
175
|
+
function getFixedLayoutMultiPortalRecords() {
|
|
176
|
+
return [
|
|
177
|
+
{
|
|
178
|
+
uid: import_constants.DEFAULT_ADMIN_MULTI_PORTAL_UID,
|
|
179
|
+
title: "Desktop layout",
|
|
180
|
+
icon: "DesktopOutlined",
|
|
181
|
+
portalType: "no-code",
|
|
182
|
+
portalName: "admin",
|
|
183
|
+
routePath: "/admin",
|
|
184
|
+
authCheck: true,
|
|
185
|
+
enabled: true,
|
|
186
|
+
uiLayoutUid: import_constants.ADMIN_UI_LAYOUT_UID
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
uid: import_constants.DEFAULT_MOBILE_MULTI_PORTAL_UID,
|
|
190
|
+
title: "Mobile layout",
|
|
191
|
+
icon: "MobileOutlined",
|
|
192
|
+
portalType: "no-code",
|
|
193
|
+
portalName: "mobile",
|
|
194
|
+
routePath: "/mobile",
|
|
195
|
+
authCheck: true,
|
|
196
|
+
enabled: true,
|
|
197
|
+
uiLayoutUid: import_constants.MOBILE_UI_LAYOUT_UID
|
|
198
|
+
}
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
function getFixedLayoutMultiPortalRecord(uid) {
|
|
202
|
+
return getFixedLayoutMultiPortalRecords().find((portal) => portal.uid === uid);
|
|
203
|
+
}
|
|
177
204
|
function isValidPortalDeploySegment(value) {
|
|
178
205
|
return /^[A-Za-z0-9_-]+$/.test(value);
|
|
179
206
|
}
|
|
@@ -252,6 +279,13 @@ function getPortalDeployBasePath(appName, portalName) {
|
|
|
252
279
|
const portalPath = appName === MAIN_APP_NAME ? `/${PORTAL_CLIENT_PREFIX}/${portalName}/` : `/${PORTAL_CLIENT_PREFIX}/apps/${appName}/${portalName}/`;
|
|
253
280
|
return resolvePortalStoragePublicPath(joinPortalStoragePublicPath(process.env.APP_PUBLIC_PATH || "/", portalPath));
|
|
254
281
|
}
|
|
282
|
+
function getPortalDeployBasePathCandidates(appName, portalName) {
|
|
283
|
+
const expectedBasePaths = [getPortalDeployBasePath(appName, portalName)];
|
|
284
|
+
if (appName !== MAIN_APP_NAME) {
|
|
285
|
+
expectedBasePaths.push(getPortalDeployBasePath(MAIN_APP_NAME, portalName));
|
|
286
|
+
}
|
|
287
|
+
return expectedBasePaths;
|
|
288
|
+
}
|
|
255
289
|
function createPortalDeployUploadMiddleware() {
|
|
256
290
|
const storage = import_utils.koaMulter.diskStorage({
|
|
257
291
|
destination: import_os.default.tmpdir(),
|
|
@@ -394,80 +428,13 @@ async function resolvePortalTemplate(templateSource, logPath) {
|
|
|
394
428
|
return downloadPortalTemplatePackage(templateSource, logPath);
|
|
395
429
|
}
|
|
396
430
|
async function copyPortalTemplate(sourceDir, targetDir) {
|
|
397
|
-
const ignoredSegments = /* @__PURE__ */ new Set([".git", "node_modules", ".DS_Store"]);
|
|
431
|
+
const ignoredSegments = /* @__PURE__ */ new Set([".git", "node_modules", ".DS_Store", ".env", ".env.local"]);
|
|
398
432
|
await import_fs.default.promises.mkdir(import_path.default.dirname(targetDir), { recursive: true });
|
|
399
433
|
await import_fs.default.promises.cp(sourceDir, targetDir, {
|
|
400
434
|
recursive: true,
|
|
401
435
|
filter: (source) => !import_path.default.relative(sourceDir, source).split(import_path.default.sep).some((segment) => segment.startsWith("._") || ignoredSegments.has(segment))
|
|
402
436
|
});
|
|
403
437
|
}
|
|
404
|
-
function getPortalStorageConfig(options) {
|
|
405
|
-
const sourceOptions = isRecordLike(options) ? options : {};
|
|
406
|
-
const sourceStorage = trimString(sourceOptions.sourceStorage);
|
|
407
|
-
if (sourceStorage !== "git") {
|
|
408
|
-
return { sourceStorage: "nocobase" };
|
|
409
|
-
}
|
|
410
|
-
const gitOptions = isRecordLike(sourceOptions.git) ? sourceOptions.git : {};
|
|
411
|
-
return {
|
|
412
|
-
sourceStorage,
|
|
413
|
-
git: {
|
|
414
|
-
repo: trimString(gitOptions.repo),
|
|
415
|
-
branch: trimString(gitOptions.branch) || "main",
|
|
416
|
-
path: trimString(gitOptions.path) || "."
|
|
417
|
-
}
|
|
418
|
-
};
|
|
419
|
-
}
|
|
420
|
-
function upsertPortalEnvContent(content, values) {
|
|
421
|
-
const nextValues = { ...values };
|
|
422
|
-
const lines = content ? content.replace(/\r\n/g, "\n").split("\n") : [];
|
|
423
|
-
const result = [];
|
|
424
|
-
for (const line of lines) {
|
|
425
|
-
if (!line && result.length === lines.length - 1) {
|
|
426
|
-
continue;
|
|
427
|
-
}
|
|
428
|
-
const match = line.match(/^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
429
|
-
const key = match == null ? void 0 : match[2];
|
|
430
|
-
if (key && Object.prototype.hasOwnProperty.call(nextValues, key)) {
|
|
431
|
-
result.push(`${key}=${nextValues[key]}`);
|
|
432
|
-
delete nextValues[key];
|
|
433
|
-
continue;
|
|
434
|
-
}
|
|
435
|
-
result.push(line);
|
|
436
|
-
}
|
|
437
|
-
for (const [key, value] of Object.entries(nextValues)) {
|
|
438
|
-
result.push(`${key}=${value}`);
|
|
439
|
-
}
|
|
440
|
-
return `${result.join("\n").replace(/\n*$/, "")}
|
|
441
|
-
`;
|
|
442
|
-
}
|
|
443
|
-
async function upsertPortalEnvFile(filePath, values) {
|
|
444
|
-
let content = "";
|
|
445
|
-
try {
|
|
446
|
-
content = await import_fs.default.promises.readFile(filePath, "utf-8");
|
|
447
|
-
} catch {
|
|
448
|
-
content = "";
|
|
449
|
-
}
|
|
450
|
-
await import_fs.default.promises.writeFile(filePath, upsertPortalEnvContent(content, values), "utf-8");
|
|
451
|
-
}
|
|
452
|
-
async function ensurePortalStorageConfigFiles(portalDir, item) {
|
|
453
|
-
const apiUrl = getPortalStorageApiUrl(item.appName);
|
|
454
|
-
const portalBase = getPortalDeployBasePath(item.appName, item.portalName);
|
|
455
|
-
await import_fs.default.promises.mkdir(portalDir, { recursive: true });
|
|
456
|
-
await upsertPortalEnvFile(import_path.default.join(portalDir, ".env"), {
|
|
457
|
-
NOCOBASE_API_URL: resolvePortalStorageEnvApiUrl(apiUrl),
|
|
458
|
-
NOCOBASE_PORTAL_BASE: portalBase
|
|
459
|
-
});
|
|
460
|
-
await upsertPortalEnvFile(import_path.default.join(portalDir, ".env.local"), {
|
|
461
|
-
NOCOBASE_API_URL: apiUrl,
|
|
462
|
-
NOCOBASE_PORTAL_BASE: portalBase
|
|
463
|
-
});
|
|
464
|
-
await import_fs.default.promises.writeFile(
|
|
465
|
-
import_path.default.join(portalDir, "portal.config.json"),
|
|
466
|
-
`${JSON.stringify(item.config, null, 2)}
|
|
467
|
-
`,
|
|
468
|
-
"utf-8"
|
|
469
|
-
);
|
|
470
|
-
}
|
|
471
438
|
function sanitizePortalStorageNodeOptions(value) {
|
|
472
439
|
return trimString(value).split(/\s+/).filter((option) => option !== "--preserve-symlinks" && option !== "--preserve-symlinks-main").join(" ");
|
|
473
440
|
}
|
|
@@ -552,16 +519,24 @@ async function runPortalStorageCommandOnce(command, args, options) {
|
|
|
552
519
|
throw error;
|
|
553
520
|
}
|
|
554
521
|
}
|
|
555
|
-
|
|
522
|
+
function hasRequestAppHeader(options) {
|
|
523
|
+
var _a;
|
|
524
|
+
return Boolean(trimString((_a = options == null ? void 0 : options.context) == null ? void 0 : _a.get("X-App")));
|
|
525
|
+
}
|
|
526
|
+
async function buildPortalStorageItem(portalDir, item, options) {
|
|
556
527
|
const logPath = getPortalStorageLogPath(item);
|
|
528
|
+
const buildAppName = hasRequestAppHeader(options) ? item.appName : MAIN_APP_NAME;
|
|
557
529
|
const buildEnv = getPortalStorageCommandEnv({
|
|
558
|
-
NOCOBASE_API_URL: getPortalStorageApiUrl(
|
|
559
|
-
NOCOBASE_PORTAL_BASE: getPortalDeployBasePath(
|
|
530
|
+
NOCOBASE_API_URL: getPortalStorageApiUrl(buildAppName),
|
|
531
|
+
NOCOBASE_PORTAL_BASE: getPortalDeployBasePath(buildAppName, item.portalName),
|
|
532
|
+
SKIP_YARN_COREPACK_CHECK: "1",
|
|
533
|
+
COREPACK_ENABLE_STRICT: "0",
|
|
534
|
+
COREPACK_ENABLE_PROJECT_SPEC: "0"
|
|
560
535
|
});
|
|
561
536
|
await appendPortalStorageLog(logPath, `Building portal ${item.appName}/${item.portalName}.`);
|
|
562
537
|
await appendPortalStorageLog(
|
|
563
538
|
logPath,
|
|
564
|
-
`Build environment: NOCOBASE_API_URL=${buildEnv.NOCOBASE_API_URL || ""} NOCOBASE_PORTAL_BASE=${buildEnv.NOCOBASE_PORTAL_BASE || ""} APP_PUBLIC_PATH=${buildEnv.APP_PUBLIC_PATH || ""}`
|
|
539
|
+
`Build environment: NOCOBASE_API_URL=${buildEnv.NOCOBASE_API_URL || ""} NOCOBASE_PORTAL_BASE=${buildEnv.NOCOBASE_PORTAL_BASE || ""} APP_PUBLIC_PATH=${buildEnv.APP_PUBLIC_PATH || ""} SKIP_YARN_COREPACK_CHECK=${buildEnv.SKIP_YARN_COREPACK_CHECK || ""} COREPACK_ENABLE_STRICT=${buildEnv.COREPACK_ENABLE_STRICT || ""} COREPACK_ENABLE_PROJECT_SPEC=${buildEnv.COREPACK_ENABLE_PROJECT_SPEC || ""}`
|
|
565
540
|
);
|
|
566
541
|
await runPortalStorageCommandOnce("yarn", ["build:html"], {
|
|
567
542
|
cwd: portalDir,
|
|
@@ -575,9 +550,9 @@ function validatePortalDeployBasePath(appName, portalName, basePath) {
|
|
|
575
550
|
if (normalizedBasePath.includes("..")) {
|
|
576
551
|
throw new Error('basePath cannot contain ".."');
|
|
577
552
|
}
|
|
578
|
-
const
|
|
579
|
-
if (normalizedBasePath
|
|
580
|
-
throw new Error(`basePath must be ${
|
|
553
|
+
const expectedBasePaths = getPortalDeployBasePathCandidates(appName, portalName);
|
|
554
|
+
if (!expectedBasePaths.includes(normalizedBasePath)) {
|
|
555
|
+
throw new Error(`basePath must be ${expectedBasePaths.join(" or ")}`);
|
|
581
556
|
}
|
|
582
557
|
}
|
|
583
558
|
function isPortalDeployTarEntry(entry) {
|
|
@@ -790,29 +765,6 @@ async function replacePortalSourceFromArchive(params) {
|
|
|
790
765
|
await import_fs.default.promises.rm(tarPath, { force: true });
|
|
791
766
|
}
|
|
792
767
|
}
|
|
793
|
-
function isDefaultMultiPortalUid(uid) {
|
|
794
|
-
return typeof uid === "string" && DEFAULT_MULTI_PORTAL_UID_SET.has(uid);
|
|
795
|
-
}
|
|
796
|
-
function getDefaultMultiPortalRecordForUid(uid) {
|
|
797
|
-
return isDefaultMultiPortalUid(uid) ? getDefaultMultiPortalRecord() : void 0;
|
|
798
|
-
}
|
|
799
|
-
function getDefaultMultiPortalUiLayoutUid(portal) {
|
|
800
|
-
return Object.prototype.hasOwnProperty.call(portal, "uiLayoutUid") ? portal.uiLayoutUid ?? null : null;
|
|
801
|
-
}
|
|
802
|
-
function withCustomMultiPortalFilter(filter = {}) {
|
|
803
|
-
const customFilter = { ...filter };
|
|
804
|
-
const uidFilter = customFilter.uid;
|
|
805
|
-
if (Array.isArray(uidFilter)) {
|
|
806
|
-
customFilter.uid = uidFilter.filter((uid) => !isDefaultMultiPortalUid(uid));
|
|
807
|
-
return customFilter;
|
|
808
|
-
}
|
|
809
|
-
if (isDefaultMultiPortalUid(uidFilter)) {
|
|
810
|
-
customFilter.uid = [];
|
|
811
|
-
return customFilter;
|
|
812
|
-
}
|
|
813
|
-
customFilter["uid.$notIn"] = DEFAULT_MULTI_PORTAL_UIDS;
|
|
814
|
-
return customFilter;
|
|
815
|
-
}
|
|
816
768
|
function uniqueDesktopRouteIds(routeIds) {
|
|
817
769
|
const seen = /* @__PURE__ */ new Set();
|
|
818
770
|
return routeIds.filter((routeId) => {
|
|
@@ -838,19 +790,11 @@ function collectDesktopRouteIds(record) {
|
|
|
838
790
|
}
|
|
839
791
|
return uniqueDesktopRouteIds(routeIds);
|
|
840
792
|
}
|
|
841
|
-
function pickMultiPortalUiLayoutRuntimeFields(record) {
|
|
842
|
-
const result = {};
|
|
843
|
-
for (const field of MULTI_PORTAL_UI_LAYOUT_RUNTIME_FIELDS) {
|
|
844
|
-
result[field] = getRecordField(record, field);
|
|
845
|
-
}
|
|
846
|
-
return result;
|
|
847
|
-
}
|
|
848
793
|
function pickMultiPortalRuntimeFields(record) {
|
|
849
794
|
const result = {};
|
|
850
795
|
for (const field of MULTI_PORTAL_RUNTIME_FIELDS) {
|
|
851
796
|
result[field] = getRecordField(record, field);
|
|
852
797
|
}
|
|
853
|
-
result.uiLayout = pickMultiPortalUiLayoutRuntimeFields(getRecordField(record, "uiLayout"));
|
|
854
798
|
return result;
|
|
855
799
|
}
|
|
856
800
|
function pickMultiPortalAccessibleFields(record) {
|
|
@@ -858,7 +802,6 @@ function pickMultiPortalAccessibleFields(record) {
|
|
|
858
802
|
for (const field of MULTI_PORTAL_ACCESSIBLE_FIELDS) {
|
|
859
803
|
result[field] = getRecordField(record, field) ?? null;
|
|
860
804
|
}
|
|
861
|
-
result.uiLayout = pickMultiPortalUiLayoutRuntimeFields(getRecordField(record, "uiLayout"));
|
|
862
805
|
return result;
|
|
863
806
|
}
|
|
864
807
|
function getExplicitRequestedLayoutUid(layout) {
|
|
@@ -867,14 +810,25 @@ function getExplicitRequestedLayoutUid(layout) {
|
|
|
867
810
|
return uid;
|
|
868
811
|
}
|
|
869
812
|
}
|
|
813
|
+
function hasActionParam(params, key) {
|
|
814
|
+
return isRecordLike(params) && Object.prototype.hasOwnProperty.call(params, key);
|
|
815
|
+
}
|
|
870
816
|
function getRequestedMultiPortalUid(ctx) {
|
|
871
|
-
var _a, _b;
|
|
872
|
-
const
|
|
873
|
-
const
|
|
874
|
-
|
|
817
|
+
var _a, _b, _c;
|
|
818
|
+
const hasPortalScope = hasActionParam((_a = ctx.action) == null ? void 0 : _a.params, "portal");
|
|
819
|
+
const hasLayoutScope = hasActionParam((_b = ctx.action) == null ? void 0 : _b.params, "layout");
|
|
820
|
+
const portalUid = getExplicitRequestedLayoutUid((_c = ctx.action) == null ? void 0 : _c.params.portal);
|
|
821
|
+
if (hasPortalScope && hasLayoutScope) {
|
|
875
822
|
ctx.throw(400, "layout and portal cannot be used together");
|
|
876
823
|
return;
|
|
877
824
|
}
|
|
825
|
+
if (!hasPortalScope) {
|
|
826
|
+
return;
|
|
827
|
+
}
|
|
828
|
+
if (!portalUid) {
|
|
829
|
+
ctx.throw(400, "Invalid portal scope");
|
|
830
|
+
return;
|
|
831
|
+
}
|
|
878
832
|
return portalUid;
|
|
879
833
|
}
|
|
880
834
|
function getCurrentRoles(ctx) {
|
|
@@ -899,27 +853,88 @@ function getMultiPortalNameFromValues(ctx) {
|
|
|
899
853
|
return portalName;
|
|
900
854
|
}
|
|
901
855
|
}
|
|
902
|
-
function
|
|
856
|
+
function getMultiPortalWriteRecords(ctx) {
|
|
903
857
|
var _a;
|
|
904
858
|
const values = (_a = ctx.action) == null ? void 0 : _a.params.values;
|
|
905
|
-
|
|
906
|
-
|
|
859
|
+
const records = Array.isArray(values) ? values : [values];
|
|
860
|
+
return records.filter(
|
|
861
|
+
(record) => !!record && typeof record === "object" && !Array.isArray(record)
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
async function getMultiPortalWriteTargets(ctx, fields) {
|
|
865
|
+
var _a, _b, _c, _d, _e;
|
|
866
|
+
const records = getMultiPortalWriteRecords(ctx);
|
|
867
|
+
if (!records.length || ((_a = ctx.action) == null ? void 0 : _a.actionName) === "create") {
|
|
868
|
+
return records.map((values2) => ({ values: values2 }));
|
|
869
|
+
}
|
|
870
|
+
const repository = ctx.db.getRepository("multiPortals");
|
|
871
|
+
const actionName = (_b = ctx.action) == null ? void 0 : _b.actionName;
|
|
872
|
+
const requestedFilterKeys = (_c = ctx.action) == null ? void 0 : _c.params.filterKeys;
|
|
873
|
+
if ((actionName === "firstOrCreate" || actionName === "updateOrCreate") && Array.isArray(requestedFilterKeys) && requestedFilterKeys.every((filterKey) => typeof filterKey === "string")) {
|
|
874
|
+
const targets = [];
|
|
875
|
+
for (const values2 of records) {
|
|
876
|
+
const existing2 = await repository.findOne({
|
|
877
|
+
filter: import_database.Repository.valuesToFilter(values2, requestedFilterKeys),
|
|
878
|
+
fields
|
|
879
|
+
});
|
|
880
|
+
targets.push({ existing: existing2, values: values2 });
|
|
881
|
+
}
|
|
882
|
+
return targets;
|
|
883
|
+
}
|
|
884
|
+
if (records.length > 1) {
|
|
885
|
+
const targets = [];
|
|
886
|
+
for (const values2 of records) {
|
|
887
|
+
const uid2 = values2.uid;
|
|
888
|
+
const existing2 = typeof uid2 === "string" && uid2 ? await repository.findOne({
|
|
889
|
+
filterByTk: uid2,
|
|
890
|
+
fields
|
|
891
|
+
}) : null;
|
|
892
|
+
targets.push({ existing: existing2, values: values2 });
|
|
893
|
+
}
|
|
894
|
+
return targets;
|
|
895
|
+
}
|
|
896
|
+
const values = records[0];
|
|
897
|
+
const filterByTk = (_d = ctx.action) == null ? void 0 : _d.params.filterByTk;
|
|
898
|
+
if (filterByTk !== void 0 && filterByTk !== null) {
|
|
899
|
+
const existingRecords = await repository.find({
|
|
900
|
+
filterByTk,
|
|
901
|
+
fields
|
|
902
|
+
});
|
|
903
|
+
return existingRecords.length ? existingRecords.map((existing2) => ({ existing: existing2, values })) : [{ values }];
|
|
904
|
+
}
|
|
905
|
+
const filter = (_e = ctx.action) == null ? void 0 : _e.params.filter;
|
|
906
|
+
if (isRecordLike(filter) && !Array.isArray(filter)) {
|
|
907
|
+
const existingRecords = await repository.find({
|
|
908
|
+
filter,
|
|
909
|
+
fields
|
|
910
|
+
});
|
|
911
|
+
return existingRecords.length ? existingRecords.map((existing2) => ({ existing: existing2, values })) : [{ values }];
|
|
907
912
|
}
|
|
908
|
-
|
|
913
|
+
const uid = values.uid;
|
|
914
|
+
const existing = typeof uid === "string" && uid ? await repository.findOne({
|
|
915
|
+
filterByTk: uid,
|
|
916
|
+
fields
|
|
917
|
+
}) : null;
|
|
918
|
+
return [{ existing, values }];
|
|
909
919
|
}
|
|
910
920
|
function isTruthyActionBoolean(value) {
|
|
911
921
|
return value === true || value === 1 || value === "1" || value === "true";
|
|
912
922
|
}
|
|
913
923
|
async function captureSkipCreatePortalDirectory(ctx, next) {
|
|
914
|
-
const
|
|
915
|
-
|
|
924
|
+
const records = getMultiPortalWriteRecords(ctx);
|
|
925
|
+
const recordsWithOption = records.filter(
|
|
926
|
+
(record) => Object.prototype.hasOwnProperty.call(record, "skipCreatePortalDirectory")
|
|
927
|
+
);
|
|
928
|
+
if (!recordsWithOption.length) {
|
|
916
929
|
await next();
|
|
917
930
|
return;
|
|
918
931
|
}
|
|
919
|
-
ctx[SKIP_CREATE_PORTAL_DIRECTORY] =
|
|
920
|
-
|
|
932
|
+
ctx[SKIP_CREATE_PORTAL_DIRECTORY] = recordsWithOption.some(
|
|
933
|
+
(record) => isTruthyActionBoolean(record.skipCreatePortalDirectory)
|
|
921
934
|
);
|
|
922
|
-
|
|
935
|
+
for (const record of recordsWithOption) {
|
|
936
|
+
delete record.skipCreatePortalDirectory;
|
|
937
|
+
}
|
|
923
938
|
await next();
|
|
924
939
|
}
|
|
925
940
|
function shouldSkipCreatePortalDirectory(options) {
|
|
@@ -927,156 +942,177 @@ function shouldSkipCreatePortalDirectory(options) {
|
|
|
927
942
|
return ((_a = options == null ? void 0 : options.context) == null ? void 0 : _a[SKIP_CREATE_PORTAL_DIRECTORY]) === true;
|
|
928
943
|
}
|
|
929
944
|
async function normalizeMultiPortalSlugValues(ctx, next) {
|
|
930
|
-
const values
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
945
|
+
for (const values of getMultiPortalWriteRecords(ctx)) {
|
|
946
|
+
const portalName = values.portalName;
|
|
947
|
+
if (portalName === void 0 || portalName === null || typeof portalName !== "string") {
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
const slug = portalName.trim();
|
|
951
|
+
if (!slug) {
|
|
952
|
+
continue;
|
|
953
|
+
}
|
|
954
|
+
if (!MULTI_PORTAL_SLUG_PATTERN.test(slug)) {
|
|
955
|
+
ctx.throw(400, "Portal name can only contain lowercase letters, numbers, hyphens, and underscores");
|
|
956
|
+
return;
|
|
957
|
+
}
|
|
958
|
+
values.portalName = slug;
|
|
959
|
+
values.routePath = `/${slug}`;
|
|
939
960
|
}
|
|
940
|
-
const
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
961
|
+
const repository = ctx.db.getRepository("multiPortals");
|
|
962
|
+
const pendingPortalNames = /* @__PURE__ */ new Set();
|
|
963
|
+
const targets = await getMultiPortalWriteTargets(ctx, ["uid"]);
|
|
964
|
+
for (const { existing, values } of targets) {
|
|
965
|
+
const portalName = values.portalName;
|
|
966
|
+
if (typeof portalName !== "string" || !portalName) {
|
|
967
|
+
continue;
|
|
968
|
+
}
|
|
969
|
+
if (pendingPortalNames.has(portalName)) {
|
|
970
|
+
ctx.throw(409, ctx.t('Portal name "{{portalName}}" already exists', { ns: import_constants.NAMESPACE, portalName }));
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
pendingPortalNames.add(portalName);
|
|
974
|
+
const conflict = await repository.findOne({
|
|
975
|
+
filter: { portalName },
|
|
976
|
+
fields: ["uid"]
|
|
977
|
+
});
|
|
978
|
+
if (conflict && conflict.get("uid") !== (existing == null ? void 0 : existing.get("uid"))) {
|
|
979
|
+
ctx.throw(409, ctx.t('Portal name "{{portalName}}" already exists', { ns: import_constants.NAMESPACE, portalName }));
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
944
982
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
983
|
+
await next();
|
|
984
|
+
}
|
|
985
|
+
async function preventDirectDefaultPortalMutation(ctx, next) {
|
|
986
|
+
for (const values of getMultiPortalWriteRecords(ctx)) {
|
|
987
|
+
if (Object.prototype.hasOwnProperty.call(values, "isDefault")) {
|
|
988
|
+
ctx.throw(400, ctx.t("Use multiPortals:setDefault to update the default Portal", { ns: import_constants.NAMESPACE }));
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
948
991
|
}
|
|
949
|
-
values.portalName = slug;
|
|
950
|
-
values.routePath = `/${slug}`;
|
|
951
992
|
await next();
|
|
952
993
|
}
|
|
953
|
-
|
|
994
|
+
function isUniqueConstraintViolation(error) {
|
|
995
|
+
return error instanceof import_database.UniqueConstraintError || isRecordLike(error) && error.name === "SequelizeUniqueConstraintError";
|
|
996
|
+
}
|
|
997
|
+
async function createDefaultMultiPortalBestEffort(db, portal) {
|
|
954
998
|
const repository = db.getRepository("multiPortals");
|
|
955
|
-
const
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
return;
|
|
999
|
+
const [uidConflict, portalNameConflict] = await Promise.all([
|
|
1000
|
+
repository.findOne({
|
|
1001
|
+
filterByTk: portal.uid,
|
|
1002
|
+
fields: ["uid"]
|
|
1003
|
+
}),
|
|
1004
|
+
repository.findOne({
|
|
1005
|
+
filter: { portalName: portal.portalName },
|
|
1006
|
+
fields: ["uid"]
|
|
1007
|
+
})
|
|
1008
|
+
]);
|
|
1009
|
+
if (uidConflict || portalNameConflict) {
|
|
1010
|
+
return false;
|
|
967
1011
|
}
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
1012
|
+
try {
|
|
1013
|
+
await repository.create({ values: portal });
|
|
1014
|
+
return true;
|
|
1015
|
+
} catch (error) {
|
|
1016
|
+
if (isUniqueConstraintViolation(error)) {
|
|
1017
|
+
return false;
|
|
1018
|
+
}
|
|
1019
|
+
throw error;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
async function repairFixedLayoutMultiPortalRecords(db) {
|
|
1023
|
+
const repository = db.getRepository("multiPortals");
|
|
1024
|
+
for (const portal of getFixedLayoutMultiPortalRecords()) {
|
|
1025
|
+
const existing = await repository.findOne({
|
|
1026
|
+
filterByTk: portal.uid,
|
|
1027
|
+
fields: ["uid", "portalType", "portalName", "routePath", "uiLayoutUid"]
|
|
1028
|
+
});
|
|
1029
|
+
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) {
|
|
1030
|
+
continue;
|
|
1031
|
+
}
|
|
978
1032
|
await repository.update({
|
|
979
|
-
filterByTk:
|
|
980
|
-
values:
|
|
981
|
-
|
|
1033
|
+
filterByTk: portal.uid,
|
|
1034
|
+
values: {
|
|
1035
|
+
portalType: portal.portalType
|
|
1036
|
+
}
|
|
982
1037
|
});
|
|
983
1038
|
}
|
|
984
1039
|
}
|
|
985
|
-
async function
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
if (!defaultPortal) {
|
|
989
|
-
await next();
|
|
990
|
-
return;
|
|
1040
|
+
async function seedFreshMultiPortals(db) {
|
|
1041
|
+
for (const portal of getFreshMultiPortalRecords()) {
|
|
1042
|
+
await createDefaultMultiPortalBestEffort(db, portal);
|
|
991
1043
|
}
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
portalName: defaultPortal.portalName,
|
|
998
|
-
routePath: defaultPortal.routePath,
|
|
999
|
-
authCheck: defaultPortal.authCheck,
|
|
1000
|
-
uiLayoutUid: getDefaultMultiPortalUiLayoutUid(defaultPortal)
|
|
1001
|
-
};
|
|
1002
|
-
if (Object.prototype.hasOwnProperty.call(rawValues, "title")) {
|
|
1003
|
-
values.title = rawValues.title;
|
|
1004
|
-
}
|
|
1005
|
-
if (Object.prototype.hasOwnProperty.call(rawValues, "icon")) {
|
|
1006
|
-
values.icon = rawValues.icon;
|
|
1007
|
-
}
|
|
1008
|
-
if (Object.prototype.hasOwnProperty.call(rawValues, "enabled")) {
|
|
1009
|
-
values.enabled = rawValues.enabled;
|
|
1044
|
+
}
|
|
1045
|
+
async function seedHistoricalMultiPortals(db) {
|
|
1046
|
+
await createDefaultMultiPortalBestEffort(db, getDefaultAiMultiPortalRecord());
|
|
1047
|
+
for (const portal of getFixedLayoutMultiPortalRecords()) {
|
|
1048
|
+
await createDefaultMultiPortalBestEffort(db, portal);
|
|
1010
1049
|
}
|
|
1011
|
-
(
|
|
1012
|
-
{
|
|
1013
|
-
values
|
|
1014
|
-
},
|
|
1015
|
-
{
|
|
1016
|
-
values: "overwrite"
|
|
1017
|
-
}
|
|
1018
|
-
);
|
|
1019
|
-
await next();
|
|
1050
|
+
await repairFixedLayoutMultiPortalRecords(db);
|
|
1020
1051
|
}
|
|
1021
|
-
async function
|
|
1052
|
+
async function validateMultiPortalUiLayoutUidWrite(ctx, next) {
|
|
1022
1053
|
var _a;
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1054
|
+
const targets = await getMultiPortalWriteTargets(ctx, ["uiLayoutUid"]);
|
|
1055
|
+
const actionName = (_a = ctx.action) == null ? void 0 : _a.actionName;
|
|
1056
|
+
const createsWhenMissing = actionName === "create" || actionName === "firstOrCreate" || actionName === "updateOrCreate";
|
|
1057
|
+
for (const { existing, values } of targets) {
|
|
1058
|
+
const hasUiLayoutUid = Object.prototype.hasOwnProperty.call(values, "uiLayoutUid");
|
|
1059
|
+
if (!existing && createsWhenMissing && !hasUiLayoutUid) {
|
|
1060
|
+
ctx.throw(400, `Portal UI layout must be one of: ${import_constants.MULTI_PORTAL_UI_LAYOUT_UIDS.join(", ")}`);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
if (!hasUiLayoutUid) {
|
|
1064
|
+
continue;
|
|
1065
|
+
}
|
|
1066
|
+
if (!(0, import_constants.isMultiPortalUiLayoutUid)(values.uiLayoutUid)) {
|
|
1067
|
+
ctx.throw(400, `Portal UI layout must be one of: ${import_constants.MULTI_PORTAL_UI_LAYOUT_UIDS.join(", ")}`);
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
const existingUiLayoutUid = existing == null ? void 0 : existing.get("uiLayoutUid");
|
|
1071
|
+
if (existing && existingUiLayoutUid !== values.uiLayoutUid) {
|
|
1072
|
+
ctx.throw(400, "Portal UI layout cannot be changed");
|
|
1073
|
+
return;
|
|
1074
|
+
}
|
|
1041
1075
|
}
|
|
1042
1076
|
await next();
|
|
1043
1077
|
}
|
|
1044
|
-
async function findRequestedMultiPortal(ctx) {
|
|
1045
|
-
var _a;
|
|
1078
|
+
async function findRequestedMultiPortal(ctx, transaction) {
|
|
1046
1079
|
const portalUid = getRequestedMultiPortalUid(ctx);
|
|
1047
1080
|
if (portalUid === void 0) {
|
|
1048
1081
|
return {
|
|
1049
1082
|
requested: false
|
|
1050
1083
|
};
|
|
1051
1084
|
}
|
|
1052
|
-
const isExplicitPortalRequest = getExplicitRequestedLayoutUid((_a = ctx.action) == null ? void 0 : _a.params.portal) !== void 0;
|
|
1053
|
-
if (!isExplicitPortalRequest) {
|
|
1054
|
-
const uiLayout = await ctx.db.getRepository("uiLayouts").findOne({
|
|
1055
|
-
filter: {
|
|
1056
|
-
uid: portalUid
|
|
1057
|
-
},
|
|
1058
|
-
fields: ["uid"]
|
|
1059
|
-
});
|
|
1060
|
-
if (uiLayout) {
|
|
1061
|
-
return {
|
|
1062
|
-
requested: false
|
|
1063
|
-
};
|
|
1064
|
-
}
|
|
1065
|
-
}
|
|
1066
1085
|
const portal = await ctx.db.getRepository("multiPortals").findOne({
|
|
1067
1086
|
filter: {
|
|
1068
1087
|
uid: portalUid
|
|
1069
1088
|
},
|
|
1070
|
-
fields: ["uid", "uiLayoutUid", "enabled"]
|
|
1089
|
+
fields: ["uid", "portalType", "uiLayoutUid", "enabled"],
|
|
1090
|
+
...transaction ? { lock: transaction.LOCK.UPDATE } : {},
|
|
1091
|
+
transaction
|
|
1071
1092
|
});
|
|
1072
|
-
if (
|
|
1073
|
-
return {
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
};
|
|
1093
|
+
if (!portal || portal.get("enabled") !== true) {
|
|
1094
|
+
return { requested: true };
|
|
1095
|
+
}
|
|
1096
|
+
if (portal.get("portalType") !== "no-code") {
|
|
1097
|
+
ctx.throw(400, `Portal '${portalUid}' does not support desktop routes`);
|
|
1077
1098
|
}
|
|
1099
|
+
const uiLayoutUid = portal.get("uiLayoutUid");
|
|
1100
|
+
if (!(0, import_constants.isMultiPortalUiLayoutUid)(uiLayoutUid)) {
|
|
1101
|
+
ctx.throw(400, `Portal '${portalUid}' has an unsupported UI layout UID`);
|
|
1102
|
+
}
|
|
1103
|
+
const usesLayoutPermissions = (0, import_constants.isDefaultLayoutMultiPortalUid)(portalUid);
|
|
1078
1104
|
return {
|
|
1079
|
-
|
|
1105
|
+
portal,
|
|
1106
|
+
requested: true,
|
|
1107
|
+
scope: {
|
|
1108
|
+
portalUid,
|
|
1109
|
+
uiLayoutUid,
|
|
1110
|
+
relation: usesLayoutPermissions ? "uiLayouts" : "multiPortals",
|
|
1111
|
+
uid: usesLayoutPermissions ? uiLayoutUid : portalUid,
|
|
1112
|
+
filter: usesLayoutPermissions ? {
|
|
1113
|
+
"uiLayouts.uid": uiLayoutUid
|
|
1114
|
+
} : getDesktopRoutePortalFilter(portalUid)
|
|
1115
|
+
}
|
|
1080
1116
|
};
|
|
1081
1117
|
}
|
|
1082
1118
|
function getDesktopRoutePortalFilter(multiPortalUid) {
|
|
@@ -1087,49 +1123,88 @@ function getDesktopRoutePortalFilter(multiPortalUid) {
|
|
|
1087
1123
|
function isDesktopRouteCreateValue(value) {
|
|
1088
1124
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
1089
1125
|
}
|
|
1090
|
-
function
|
|
1091
|
-
|
|
1126
|
+
function withDesktopRouteEffectiveOwner(value, scope) {
|
|
1127
|
+
if (Array.isArray(value)) {
|
|
1128
|
+
return value.map((item) => withDesktopRouteEffectiveOwner(item, scope));
|
|
1129
|
+
}
|
|
1130
|
+
if (!isDesktopRouteCreateValue(value)) {
|
|
1131
|
+
return value;
|
|
1132
|
+
}
|
|
1133
|
+
const { uiLayouts: _uiLayouts, multiPortals: _multiPortals, children, ...route } = value;
|
|
1134
|
+
return {
|
|
1135
|
+
...route,
|
|
1136
|
+
...scope.relation === "uiLayouts" ? { uiLayouts: [scope.uid] } : { multiPortals: [scope.uid] },
|
|
1137
|
+
...Array.isArray(children) ? { children: withDesktopRouteEffectiveOwner(children, scope) } : {}
|
|
1138
|
+
};
|
|
1092
1139
|
}
|
|
1093
|
-
function
|
|
1140
|
+
function collectExplicitDesktopRouteParentIds(value) {
|
|
1094
1141
|
if (Array.isArray(value)) {
|
|
1095
|
-
return value.
|
|
1142
|
+
return uniqueDesktopRouteIds(value.flatMap((item) => collectExplicitDesktopRouteParentIds(item)));
|
|
1096
1143
|
}
|
|
1097
1144
|
if (!isDesktopRouteCreateValue(value)) {
|
|
1098
|
-
return;
|
|
1145
|
+
return [];
|
|
1099
1146
|
}
|
|
1100
|
-
const
|
|
1101
|
-
if (
|
|
1102
|
-
|
|
1147
|
+
const parentIds = [];
|
|
1148
|
+
if (typeof value.parentId === "string" || typeof value.parentId === "number") {
|
|
1149
|
+
parentIds.push(value.parentId);
|
|
1103
1150
|
}
|
|
1104
1151
|
if (Array.isArray(value.children)) {
|
|
1105
|
-
|
|
1152
|
+
parentIds.push(...value.children.flatMap((child) => collectExplicitDesktopRouteParentIds(child)));
|
|
1106
1153
|
}
|
|
1107
|
-
return
|
|
1154
|
+
return uniqueDesktopRouteIds(parentIds);
|
|
1108
1155
|
}
|
|
1109
|
-
function
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
return
|
|
1156
|
+
async function assertDesktopRouteParentsBelongToScope(ctx, scope, values = ((_a) => (_a = ctx.action) == null ? void 0 : _a.params.values)(), transaction) {
|
|
1157
|
+
const parentIds = collectExplicitDesktopRouteParentIds(values);
|
|
1158
|
+
if (!parentIds.length) {
|
|
1159
|
+
return;
|
|
1113
1160
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1161
|
+
const parents = await ctx.db.getRepository("desktopRoutes").find({
|
|
1162
|
+
fields: ["id"],
|
|
1163
|
+
filter: {
|
|
1164
|
+
id: parentIds,
|
|
1165
|
+
...scope.filter
|
|
1166
|
+
},
|
|
1167
|
+
transaction
|
|
1168
|
+
});
|
|
1169
|
+
if (new Set(normalizeDesktopRouteTargetIds(parents)).size !== new Set(parentIds.map(String)).size) {
|
|
1170
|
+
ctx.throw(400, "Parent route does not belong to the requested portal scope");
|
|
1116
1171
|
}
|
|
1117
|
-
const explicitRoute = isDesktopRouteCreateValue(explicitValue) ? explicitValue : void 0;
|
|
1118
|
-
const existingMultiPortals = Array.isArray(value.multiPortals) ? value.multiPortals.filter((uid) => typeof uid === "string" && !!uid) : [];
|
|
1119
|
-
const { uiLayouts: _uiLayouts, children: _children, ...route } = value;
|
|
1120
|
-
return {
|
|
1121
|
-
...route,
|
|
1122
|
-
...hasDesktopRouteField(explicitRoute, "uiLayouts") ? { uiLayouts: explicitRoute == null ? void 0 : explicitRoute.uiLayouts } : {},
|
|
1123
|
-
multiPortals: Array.from(/* @__PURE__ */ new Set([...existingMultiPortals, multiPortalUid])),
|
|
1124
|
-
...Array.isArray(value.children) ? { children: withDesktopRouteMultiPortal(value.children, multiPortalUid, explicitRoute == null ? void 0 : explicitRoute.children) } : {}
|
|
1125
|
-
};
|
|
1126
1172
|
}
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1173
|
+
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)()) {
|
|
1174
|
+
const filterKeys = Array.isArray(requestedFilterKeys) ? requestedFilterKeys.filter((key) => typeof key === "string" && !!key) : [];
|
|
1175
|
+
if (!filterKeys.length) {
|
|
1176
|
+
return [];
|
|
1177
|
+
}
|
|
1178
|
+
const records = Array.isArray(values) ? values : [values];
|
|
1179
|
+
return records.flatMap((value) => {
|
|
1180
|
+
if (!isDesktopRouteCreateValue(value) || filterKeys.some((key) => !Object.prototype.hasOwnProperty.call(value, key))) {
|
|
1181
|
+
return [];
|
|
1182
|
+
}
|
|
1183
|
+
return [Object.fromEntries(filterKeys.map((key) => [key, value[key]]))];
|
|
1184
|
+
});
|
|
1185
|
+
}
|
|
1186
|
+
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) {
|
|
1187
|
+
for (const filter of getDesktopRouteUpdateOrCreateFilters(ctx, values, filterKeys)) {
|
|
1188
|
+
const allMatches = await ctx.db.getRepository("desktopRoutes").find({
|
|
1189
|
+
fields: ["id"],
|
|
1190
|
+
filter,
|
|
1191
|
+
...transaction ? { lock: transaction.LOCK.UPDATE } : {},
|
|
1192
|
+
transaction
|
|
1193
|
+
});
|
|
1194
|
+
const scopedMatches = await ctx.db.getRepository("desktopRoutes").find({
|
|
1195
|
+
fields: ["id"],
|
|
1196
|
+
filter: {
|
|
1197
|
+
...filter,
|
|
1198
|
+
...scope.filter
|
|
1199
|
+
},
|
|
1200
|
+
transaction
|
|
1201
|
+
});
|
|
1202
|
+
const allMatchIds = normalizeDesktopRouteTargetIds(allMatches);
|
|
1203
|
+
const scopedMatchIds = normalizeDesktopRouteTargetIds(scopedMatches);
|
|
1204
|
+
if (allMatchIds.join(",") !== scopedMatchIds.join(",")) {
|
|
1205
|
+
ctx.throw(400, "Desktop route does not belong to the requested portal scope");
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1133
1208
|
}
|
|
1134
1209
|
async function canAccessMultiPortal(ctx, multiPortalUid) {
|
|
1135
1210
|
const currentRoles = getCurrentRoles(ctx);
|
|
@@ -1139,13 +1214,77 @@ async function canAccessMultiPortal(ctx, multiPortalUid) {
|
|
|
1139
1214
|
if (!currentRoles.length) {
|
|
1140
1215
|
return false;
|
|
1141
1216
|
}
|
|
1142
|
-
const
|
|
1217
|
+
const access = await ctx.db.getRepository("rolesMultiPortals").findOne({
|
|
1218
|
+
fields: ["id"],
|
|
1143
1219
|
filter: {
|
|
1144
1220
|
roleName: currentRoles,
|
|
1145
1221
|
multiPortalUid
|
|
1146
1222
|
}
|
|
1147
1223
|
});
|
|
1148
|
-
return
|
|
1224
|
+
return access !== null;
|
|
1225
|
+
}
|
|
1226
|
+
function throwPortalAccessGateError(ctx, status, code, message) {
|
|
1227
|
+
ctx.throw(status, ctx.t(message, { ns: import_constants.NAMESPACE }), { code });
|
|
1228
|
+
throw new Error(message);
|
|
1229
|
+
}
|
|
1230
|
+
function getRequestedPortalNameFromHeader(ctx) {
|
|
1231
|
+
var _a;
|
|
1232
|
+
const headers = (_a = ctx.request) == null ? void 0 : _a.headers;
|
|
1233
|
+
if (!isRecordLike(headers) || !Object.prototype.hasOwnProperty.call(headers, "x-portal")) {
|
|
1234
|
+
return;
|
|
1235
|
+
}
|
|
1236
|
+
const rawPortalName = headers["x-portal"];
|
|
1237
|
+
if (typeof rawPortalName !== "string") {
|
|
1238
|
+
throwPortalAccessGateError(ctx, 400, PORTAL_CONTEXT_INVALID_CODE, "Invalid Portal context");
|
|
1239
|
+
}
|
|
1240
|
+
const portalName = rawPortalName.startsWith("/x/") ? rawPortalName.slice("/x/".length) : rawPortalName;
|
|
1241
|
+
if (!MULTI_PORTAL_SLUG_PATTERN.test(portalName)) {
|
|
1242
|
+
throwPortalAccessGateError(ctx, 400, PORTAL_CONTEXT_INVALID_CODE, "Invalid Portal context");
|
|
1243
|
+
}
|
|
1244
|
+
return portalName;
|
|
1245
|
+
}
|
|
1246
|
+
function pickPortalAccessDeniedData(ctx, portalName) {
|
|
1247
|
+
const roleCheckBody = isRecordLike(ctx.body) ? ctx.body : {};
|
|
1248
|
+
return {
|
|
1249
|
+
portalName,
|
|
1250
|
+
role: typeof roleCheckBody.role === "string" ? roleCheckBody.role : "",
|
|
1251
|
+
roles: Array.isArray(roleCheckBody.roles) ? roleCheckBody.roles.filter((role) => typeof role === "string") : [],
|
|
1252
|
+
roleMode: typeof roleCheckBody.roleMode === "string" ? roleCheckBody.roleMode : "default",
|
|
1253
|
+
allowAnonymous: roleCheckBody.allowAnonymous === true
|
|
1254
|
+
};
|
|
1255
|
+
}
|
|
1256
|
+
async function checkMultiPortalAccessForRolesCheck(ctx, next) {
|
|
1257
|
+
const portalName = getRequestedPortalNameFromHeader(ctx);
|
|
1258
|
+
if (portalName === void 0) {
|
|
1259
|
+
await next();
|
|
1260
|
+
return;
|
|
1261
|
+
}
|
|
1262
|
+
const portal = await ctx.db.getRepository("multiPortals").findOne({
|
|
1263
|
+
filter: {
|
|
1264
|
+
portalName
|
|
1265
|
+
},
|
|
1266
|
+
fields: ["uid", "portalType", "enabled", "uiLayoutUid"]
|
|
1267
|
+
});
|
|
1268
|
+
if (!portal || portal.get("enabled") !== true || !getDefaultMultiPortalType(portal) || !(0, import_constants.isMultiPortalUiLayoutUid)(portal.get("uiLayoutUid"))) {
|
|
1269
|
+
throwPortalAccessGateError(ctx, 404, PORTAL_NOT_FOUND_CODE, "Portal not found");
|
|
1270
|
+
}
|
|
1271
|
+
const portalUid = String(portal.get("uid"));
|
|
1272
|
+
if ((0, import_constants.isDefaultLayoutMultiPortalUid)(portalUid) || await canAccessMultiPortal(ctx, portalUid)) {
|
|
1273
|
+
await next();
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
await next();
|
|
1277
|
+
ctx.status = 403;
|
|
1278
|
+
ctx.withoutDataWrapping = true;
|
|
1279
|
+
ctx.body = {
|
|
1280
|
+
errors: [
|
|
1281
|
+
{
|
|
1282
|
+
code: PORTAL_ACCESS_DENIED_CODE,
|
|
1283
|
+
message: ctx.t("You do not have access to this Portal", { ns: import_constants.NAMESPACE })
|
|
1284
|
+
}
|
|
1285
|
+
],
|
|
1286
|
+
data: pickPortalAccessDeniedData(ctx, portalName)
|
|
1287
|
+
};
|
|
1149
1288
|
}
|
|
1150
1289
|
async function listCurrentRoleAccessibleMultiPortalUids(ctx) {
|
|
1151
1290
|
const currentRoles = getCurrentRoles(ctx);
|
|
@@ -1208,7 +1347,7 @@ async function removeRouteIdsWithUnauthorizedAncestors(ctx, routeIds) {
|
|
|
1208
1347
|
}
|
|
1209
1348
|
}
|
|
1210
1349
|
}
|
|
1211
|
-
async function getMultiPortalAccessibleRouteIds(ctx,
|
|
1350
|
+
async function getMultiPortalAccessibleRouteIds(ctx, portalContext) {
|
|
1212
1351
|
const currentRoles = getCurrentRoles(ctx);
|
|
1213
1352
|
if (currentRoles.includes("root")) {
|
|
1214
1353
|
return;
|
|
@@ -1216,11 +1355,11 @@ async function getMultiPortalAccessibleRouteIds(ctx, multiPortalUid) {
|
|
|
1216
1355
|
if (!currentRoles.length) {
|
|
1217
1356
|
return /* @__PURE__ */ new Set();
|
|
1218
1357
|
}
|
|
1219
|
-
const routePermissions = await ctx.db.getRepository("rolesMultiPortalDesktopRoutes").find({
|
|
1358
|
+
const routePermissions = await ctx.db.getRepository(portalContext.relation === "uiLayouts" ? "rolesDesktopRoutes" : "rolesMultiPortalDesktopRoutes").find({
|
|
1220
1359
|
fields: ["desktopRouteId"],
|
|
1221
1360
|
filter: {
|
|
1222
1361
|
roleName: currentRoles,
|
|
1223
|
-
multiPortalUid
|
|
1362
|
+
...portalContext.relation === "multiPortals" ? { multiPortalUid: portalContext.portalUid } : {}
|
|
1224
1363
|
}
|
|
1225
1364
|
});
|
|
1226
1365
|
const routeIds = /* @__PURE__ */ new Set();
|
|
@@ -1236,7 +1375,7 @@ async function getMultiPortalAccessibleRouteIds(ctx, multiPortalUid) {
|
|
|
1236
1375
|
const portalRoutes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1237
1376
|
fields: ["id"],
|
|
1238
1377
|
filter: {
|
|
1239
|
-
...
|
|
1378
|
+
...portalContext.filter,
|
|
1240
1379
|
id: Array.from(routeIds)
|
|
1241
1380
|
}
|
|
1242
1381
|
});
|
|
@@ -1266,25 +1405,123 @@ function setDesktopRouteChildren(route, children) {
|
|
|
1266
1405
|
const maybeModel = route;
|
|
1267
1406
|
if (typeof maybeModel.setDataValue === "function") {
|
|
1268
1407
|
maybeModel.setDataValue("children", children);
|
|
1408
|
+
} else {
|
|
1409
|
+
maybeModel.children = children;
|
|
1410
|
+
}
|
|
1411
|
+
if (!maybeModel._options) {
|
|
1412
|
+
return;
|
|
1413
|
+
}
|
|
1414
|
+
if (!maybeModel._options.includeNames) {
|
|
1415
|
+
maybeModel._options.includeNames = ["children"];
|
|
1269
1416
|
return;
|
|
1270
1417
|
}
|
|
1271
|
-
|
|
1418
|
+
if (!maybeModel._options.includeNames.includes("children")) {
|
|
1419
|
+
maybeModel._options.includeNames.push("children");
|
|
1420
|
+
}
|
|
1272
1421
|
}
|
|
1273
|
-
function
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1422
|
+
function getDesktopRouteId(route) {
|
|
1423
|
+
const id = getRecordField(route, "id");
|
|
1424
|
+
return id === null || id === void 0 ? void 0 : String(id);
|
|
1425
|
+
}
|
|
1426
|
+
function getDesktopRouteParentId(route) {
|
|
1427
|
+
const parentId = getRecordField(route, "parentId");
|
|
1428
|
+
return parentId === null || parentId === void 0 ? void 0 : String(parentId);
|
|
1429
|
+
}
|
|
1430
|
+
function collectDesktopRouteStringIds(routes, routeIds) {
|
|
1431
|
+
for (const route of routes) {
|
|
1432
|
+
const routeId = getDesktopRouteId(route);
|
|
1433
|
+
if (routeId) {
|
|
1434
|
+
routeIds.add(routeId);
|
|
1279
1435
|
}
|
|
1280
1436
|
const children = getRecordField(route, "children");
|
|
1281
1437
|
if (Array.isArray(children)) {
|
|
1282
|
-
|
|
1438
|
+
collectDesktopRouteStringIds(children, routeIds);
|
|
1283
1439
|
}
|
|
1284
|
-
|
|
1285
|
-
}).filter((route) => route !== null);
|
|
1440
|
+
}
|
|
1286
1441
|
}
|
|
1287
|
-
|
|
1442
|
+
function removeNestedRootDesktopRoutes(routes) {
|
|
1443
|
+
if (!Array.isArray(routes)) {
|
|
1444
|
+
return [];
|
|
1445
|
+
}
|
|
1446
|
+
const routeIds = /* @__PURE__ */ new Set();
|
|
1447
|
+
collectDesktopRouteStringIds(routes, routeIds);
|
|
1448
|
+
return routes.filter((route) => {
|
|
1449
|
+
const parentId = getDesktopRouteParentId(route);
|
|
1450
|
+
return !parentId || !routeIds.has(parentId);
|
|
1451
|
+
});
|
|
1452
|
+
}
|
|
1453
|
+
function buildAccessibleDesktopRouteTreeWithAncestors(routes, accessibleRouteIds) {
|
|
1454
|
+
const routeById = /* @__PURE__ */ new Map();
|
|
1455
|
+
const childrenByParentId = /* @__PURE__ */ new Map();
|
|
1456
|
+
const roots = [];
|
|
1457
|
+
for (const route of routes) {
|
|
1458
|
+
const routeId = getDesktopRouteId(route);
|
|
1459
|
+
if (!routeId) {
|
|
1460
|
+
continue;
|
|
1461
|
+
}
|
|
1462
|
+
setDesktopRouteChildren(route, void 0);
|
|
1463
|
+
routeById.set(routeId, route);
|
|
1464
|
+
}
|
|
1465
|
+
for (const route of routes) {
|
|
1466
|
+
const routeId = getDesktopRouteId(route);
|
|
1467
|
+
if (!routeId || !routeById.has(routeId)) {
|
|
1468
|
+
continue;
|
|
1469
|
+
}
|
|
1470
|
+
const parentId = getDesktopRouteParentId(route);
|
|
1471
|
+
if (!parentId || !routeById.has(parentId)) {
|
|
1472
|
+
roots.push(route);
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
const children = childrenByParentId.get(parentId) ?? [];
|
|
1476
|
+
children.push(route);
|
|
1477
|
+
childrenByParentId.set(parentId, children);
|
|
1478
|
+
}
|
|
1479
|
+
const visitRoute = (route, visitingRouteIds) => {
|
|
1480
|
+
const routeId = getDesktopRouteId(route);
|
|
1481
|
+
if (!routeId || visitingRouteIds.has(routeId)) {
|
|
1482
|
+
return void 0;
|
|
1483
|
+
}
|
|
1484
|
+
visitingRouteIds.add(routeId);
|
|
1485
|
+
const visibleChildren = (childrenByParentId.get(routeId) ?? []).map((child) => visitRoute(child, visitingRouteIds)).filter((child) => child !== void 0);
|
|
1486
|
+
visitingRouteIds.delete(routeId);
|
|
1487
|
+
if (!accessibleRouteIds.has(routeId) && visibleChildren.length === 0) {
|
|
1488
|
+
return void 0;
|
|
1489
|
+
}
|
|
1490
|
+
setDesktopRouteChildren(route, visibleChildren.length ? visibleChildren : void 0);
|
|
1491
|
+
return route;
|
|
1492
|
+
};
|
|
1493
|
+
return roots.map((route) => visitRoute(route, /* @__PURE__ */ new Set())).filter((route) => route !== void 0);
|
|
1494
|
+
}
|
|
1495
|
+
async function includeDesktopRouteAncestorsForListAccessible(ctx, routes, portalFilter) {
|
|
1496
|
+
if (!Array.isArray(routes)) {
|
|
1497
|
+
return routes;
|
|
1498
|
+
}
|
|
1499
|
+
const accessibleRouteIds = /* @__PURE__ */ new Set();
|
|
1500
|
+
collectDesktopRouteStringIds(routes, accessibleRouteIds);
|
|
1501
|
+
if (!accessibleRouteIds.size) {
|
|
1502
|
+
return routes;
|
|
1503
|
+
}
|
|
1504
|
+
const portalRoutes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1505
|
+
sort: "sort",
|
|
1506
|
+
filter: portalFilter
|
|
1507
|
+
});
|
|
1508
|
+
return buildAccessibleDesktopRouteTreeWithAncestors(portalRoutes, accessibleRouteIds);
|
|
1509
|
+
}
|
|
1510
|
+
function removeDesktopRoutesByIds(routes, routeIds) {
|
|
1511
|
+
return routes.map((route) => {
|
|
1512
|
+
const routeId = getRecordField(route, "id");
|
|
1513
|
+
const normalizedRouteId = routeId === null || routeId === void 0 ? void 0 : String(routeId);
|
|
1514
|
+
if (normalizedRouteId && routeIds.has(normalizedRouteId)) {
|
|
1515
|
+
return null;
|
|
1516
|
+
}
|
|
1517
|
+
const children = getRecordField(route, "children");
|
|
1518
|
+
if (Array.isArray(children)) {
|
|
1519
|
+
setDesktopRouteChildren(route, removeDesktopRoutesByIds(children, routeIds));
|
|
1520
|
+
}
|
|
1521
|
+
return route;
|
|
1522
|
+
}).filter((route) => route !== null);
|
|
1523
|
+
}
|
|
1524
|
+
async function getPortalOnlyDesktopRouteIds(ctx, routeIds) {
|
|
1288
1525
|
if (!routeIds.length) {
|
|
1289
1526
|
return /* @__PURE__ */ new Set();
|
|
1290
1527
|
}
|
|
@@ -1335,23 +1572,26 @@ async function removeMultiPortalOwnedRouteFromGetResponse(ctx) {
|
|
|
1335
1572
|
ctx.body = void 0;
|
|
1336
1573
|
}
|
|
1337
1574
|
async function replaceListAccessibleRoutesWithPortalScopedRoutes(ctx, portalContext) {
|
|
1338
|
-
const routeIds = await getMultiPortalAccessibleRouteIds(ctx, portalContext
|
|
1575
|
+
const routeIds = await getMultiPortalAccessibleRouteIds(ctx, portalContext);
|
|
1339
1576
|
if (routeIds && routeIds.size === 0) {
|
|
1340
1577
|
ctx.body = [];
|
|
1341
1578
|
return;
|
|
1342
1579
|
}
|
|
1343
|
-
|
|
1580
|
+
const routes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1344
1581
|
tree: true,
|
|
1345
1582
|
sort: "sort",
|
|
1346
1583
|
filter: {
|
|
1347
|
-
...
|
|
1584
|
+
...portalContext.filter,
|
|
1348
1585
|
...routeIds ? { id: Array.from(routeIds) } : {}
|
|
1349
1586
|
}
|
|
1350
1587
|
});
|
|
1588
|
+
ctx.body = removeNestedRootDesktopRoutes(
|
|
1589
|
+
await includeDesktopRouteAncestorsForListAccessible(ctx, routes, portalContext.filter)
|
|
1590
|
+
);
|
|
1351
1591
|
}
|
|
1352
1592
|
async function replaceGetAccessibleRouteWithPortalScopedRoute(ctx, portalContext) {
|
|
1353
1593
|
var _a;
|
|
1354
|
-
const routeIds = await getMultiPortalAccessibleRouteIds(ctx, portalContext
|
|
1594
|
+
const routeIds = await getMultiPortalAccessibleRouteIds(ctx, portalContext);
|
|
1355
1595
|
if (routeIds && routeIds.size === 0) {
|
|
1356
1596
|
ctx.status = 204;
|
|
1357
1597
|
ctx.body = void 0;
|
|
@@ -1361,7 +1601,7 @@ async function replaceGetAccessibleRouteWithPortalScopedRoute(ctx, portalContext
|
|
|
1361
1601
|
sort: "sort",
|
|
1362
1602
|
filterByTk: (_a = ctx.action) == null ? void 0 : _a.params.filterByTk,
|
|
1363
1603
|
filter: {
|
|
1364
|
-
...
|
|
1604
|
+
...portalContext.filter,
|
|
1365
1605
|
...routeIds ? { id: Array.from(routeIds) } : {}
|
|
1366
1606
|
}
|
|
1367
1607
|
});
|
|
@@ -1379,25 +1619,17 @@ async function prepareAccessibleDesktopRoutesForMultiPortal(ctx) {
|
|
|
1379
1619
|
if (!portalRequest.requested) {
|
|
1380
1620
|
return;
|
|
1381
1621
|
}
|
|
1382
|
-
const
|
|
1383
|
-
if (!
|
|
1384
|
-
return null;
|
|
1385
|
-
}
|
|
1386
|
-
const portalUid = portal.get("uid");
|
|
1387
|
-
const uiLayoutUid = portal.get("uiLayoutUid");
|
|
1388
|
-
if (typeof portalUid !== "string" || !portalUid || typeof uiLayoutUid !== "string" || !uiLayoutUid) {
|
|
1622
|
+
const scope = portalRequest.scope;
|
|
1623
|
+
if (!scope) {
|
|
1389
1624
|
return null;
|
|
1390
1625
|
}
|
|
1391
|
-
if (!await canAccessMultiPortal(ctx, portalUid)) {
|
|
1626
|
+
if (scope.relation === "multiPortals" && !await canAccessMultiPortal(ctx, scope.portalUid)) {
|
|
1392
1627
|
return null;
|
|
1393
1628
|
}
|
|
1394
1629
|
if ((_a = ctx.action) == null ? void 0 : _a.params) {
|
|
1395
|
-
ctx.action.params.layout = uiLayoutUid;
|
|
1630
|
+
ctx.action.params.layout = scope.uiLayoutUid;
|
|
1396
1631
|
}
|
|
1397
|
-
return
|
|
1398
|
-
portalUid,
|
|
1399
|
-
uiLayoutUid
|
|
1400
|
-
};
|
|
1632
|
+
return scope;
|
|
1401
1633
|
}
|
|
1402
1634
|
async function addMultiPortalListAccessibleGuard(ctx, next) {
|
|
1403
1635
|
const portalContext = await prepareAccessibleDesktopRoutesForMultiPortal(ctx);
|
|
@@ -1433,98 +1665,740 @@ async function mapMultiPortalLayoutToUiLayoutForRolePermissionTargets(ctx, next)
|
|
|
1433
1665
|
ctx.body = [];
|
|
1434
1666
|
return;
|
|
1435
1667
|
}
|
|
1436
|
-
const
|
|
1437
|
-
|
|
1438
|
-
if (typeof portalUid === "string" && portalUid) {
|
|
1668
|
+
const scope = portalRequest.scope;
|
|
1669
|
+
if (scope) {
|
|
1439
1670
|
const routes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1440
|
-
tree: true,
|
|
1441
1671
|
sort: "sort",
|
|
1442
|
-
filter:
|
|
1672
|
+
filter: scope.filter,
|
|
1443
1673
|
fields: [...DESKTOP_ROUTE_ROLE_PERMISSION_TARGET_FIELDS]
|
|
1444
1674
|
});
|
|
1675
|
+
const routeIds = /* @__PURE__ */ new Set();
|
|
1676
|
+
collectDesktopRouteStringIds(routes, routeIds);
|
|
1677
|
+
const routeTree = buildAccessibleDesktopRouteTreeWithAncestors(routes, routeIds);
|
|
1445
1678
|
ctx.status = 200;
|
|
1446
|
-
ctx.body =
|
|
1679
|
+
ctx.body = removeNestedRootDesktopRoutes(routeTree).map(
|
|
1680
|
+
(route) => pickDesktopRouteRolePermissionTargetFields(route)
|
|
1681
|
+
);
|
|
1447
1682
|
return;
|
|
1448
1683
|
}
|
|
1449
1684
|
await next();
|
|
1450
1685
|
await removeMultiPortalOwnedRoutesFromListResponse(ctx);
|
|
1451
1686
|
}
|
|
1452
1687
|
async function addDesktopRouteCreateMultiPortal(ctx, next) {
|
|
1453
|
-
var _a, _b;
|
|
1688
|
+
var _a, _b, _c;
|
|
1454
1689
|
const portalRequest = await findRequestedMultiPortal(ctx);
|
|
1455
1690
|
if (portalRequest.requested && !portalRequest.portal) {
|
|
1456
1691
|
ctx.throw(400, "Invalid portal scope");
|
|
1457
1692
|
return;
|
|
1458
1693
|
}
|
|
1459
|
-
const
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1694
|
+
const scope = portalRequest.scope;
|
|
1695
|
+
if (scope) {
|
|
1696
|
+
await assertDesktopRouteParentsBelongToScope(ctx, scope);
|
|
1697
|
+
if (((_a = ctx.action) == null ? void 0 : _a.actionName) === "updateOrCreate") {
|
|
1698
|
+
await assertDesktopRouteUpsertMatchesBelongToScope(ctx, scope);
|
|
1699
|
+
}
|
|
1700
|
+
(_c = ctx.action) == null ? void 0 : _c.mergeParams(
|
|
1464
1701
|
{
|
|
1465
|
-
values:
|
|
1702
|
+
values: withDesktopRouteEffectiveOwner((_b = ctx.action) == null ? void 0 : _b.params.values, scope)
|
|
1466
1703
|
},
|
|
1467
1704
|
{
|
|
1468
1705
|
values: "overwrite"
|
|
1469
1706
|
}
|
|
1470
1707
|
);
|
|
1471
1708
|
await next();
|
|
1472
|
-
const desktopRouteIds = collectDesktopRouteIds(ctx.body);
|
|
1473
|
-
await removeLayoutRoutePermissionsFromPortalOnlyRoutes(ctx, desktopRouteIds);
|
|
1474
|
-
await grantDefaultRouteAccessToNewMultiPortalRoutes(ctx, portalUid, desktopRouteIds);
|
|
1475
1709
|
return;
|
|
1476
1710
|
}
|
|
1477
1711
|
await next();
|
|
1478
1712
|
}
|
|
1713
|
+
async function addDesktopRouteEffectiveScopeFilter(ctx, next) {
|
|
1714
|
+
var _a;
|
|
1715
|
+
const portalRequest = await findRequestedMultiPortal(ctx);
|
|
1716
|
+
if (!portalRequest.requested) {
|
|
1717
|
+
await next();
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
if (!portalRequest.scope) {
|
|
1721
|
+
ctx.throw(400, "Invalid portal scope");
|
|
1722
|
+
return;
|
|
1723
|
+
}
|
|
1724
|
+
(_a = ctx.action) == null ? void 0 : _a.mergeParams({
|
|
1725
|
+
filter: portalRequest.scope.filter
|
|
1726
|
+
});
|
|
1727
|
+
await next();
|
|
1728
|
+
}
|
|
1729
|
+
function withoutDesktopRouteOwnerFields(value) {
|
|
1730
|
+
if (Array.isArray(value)) {
|
|
1731
|
+
return value.map((item) => withoutDesktopRouteOwnerFields(item));
|
|
1732
|
+
}
|
|
1733
|
+
if (!isDesktopRouteCreateValue(value)) {
|
|
1734
|
+
return value;
|
|
1735
|
+
}
|
|
1736
|
+
const { uiLayouts: _uiLayouts, multiPortals: _multiPortals, children, ...route } = value;
|
|
1737
|
+
return {
|
|
1738
|
+
...route,
|
|
1739
|
+
...Array.isArray(children) ? { children: withoutDesktopRouteOwnerFields(children) } : {}
|
|
1740
|
+
};
|
|
1741
|
+
}
|
|
1742
|
+
function normalizeDesktopRouteTargetIds(records) {
|
|
1743
|
+
return records.map((record) => record.get("id")).filter((id) => typeof id === "string" || typeof id === "number").map(String).sort();
|
|
1744
|
+
}
|
|
1745
|
+
async function findDesktopRouteMutationTargets(ctx, scopeFilter, transaction, selector) {
|
|
1746
|
+
var _a;
|
|
1747
|
+
const params = selector ?? ((_a = ctx.action) == null ? void 0 : _a.params) ?? {};
|
|
1748
|
+
const values = params.values;
|
|
1749
|
+
const batchUpdateIds = Array.isArray(values) ? values.flatMap((value) => {
|
|
1750
|
+
if (!isDesktopRouteCreateValue(value)) {
|
|
1751
|
+
return [];
|
|
1752
|
+
}
|
|
1753
|
+
const routeId = value.id;
|
|
1754
|
+
return typeof routeId === "string" || typeof routeId === "number" ? [routeId] : [];
|
|
1755
|
+
}) : [];
|
|
1756
|
+
const hasBatchUpdateTargets = batchUpdateIds.length > 0;
|
|
1757
|
+
const filterByTk = hasBatchUpdateTargets ? batchUpdateIds : params.filterByTk;
|
|
1758
|
+
const filter = !hasBatchUpdateTargets && isRecordLike(params.filter) ? params.filter : void 0;
|
|
1759
|
+
if ((filterByTk === void 0 || filterByTk === null) && !filter) {
|
|
1760
|
+
return [];
|
|
1761
|
+
}
|
|
1762
|
+
return ctx.db.getRepository("desktopRoutes").find({
|
|
1763
|
+
fields: ["id"],
|
|
1764
|
+
...filterByTk !== void 0 && filterByTk !== null ? { filterByTk } : {},
|
|
1765
|
+
filter: {
|
|
1766
|
+
...filter ?? {},
|
|
1767
|
+
...scopeFilter ?? {}
|
|
1768
|
+
},
|
|
1769
|
+
...transaction && !scopeFilter ? { lock: transaction.LOCK.UPDATE } : {},
|
|
1770
|
+
transaction
|
|
1771
|
+
});
|
|
1772
|
+
}
|
|
1773
|
+
async function assertDesktopRouteMutationTargetsBelongToScope(ctx, scope, transaction, selector) {
|
|
1774
|
+
const allTargets = await findDesktopRouteMutationTargets(ctx, void 0, transaction, selector);
|
|
1775
|
+
const scopedTargets = await findDesktopRouteMutationTargets(ctx, scope.filter, transaction, selector);
|
|
1776
|
+
const allTargetIds = normalizeDesktopRouteTargetIds(allTargets);
|
|
1777
|
+
const scopedTargetIds = normalizeDesktopRouteTargetIds(scopedTargets);
|
|
1778
|
+
if (!allTargetIds.length || allTargetIds.join(",") !== scopedTargetIds.join(",")) {
|
|
1779
|
+
ctx.throw(400, "Desktop route does not belong to the requested portal scope");
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
async function guardDesktopRouteUpdateScope(ctx, next) {
|
|
1783
|
+
var _a, _b;
|
|
1784
|
+
const portalRequest = await findRequestedMultiPortal(ctx);
|
|
1785
|
+
if (!portalRequest.requested) {
|
|
1786
|
+
await next();
|
|
1787
|
+
return;
|
|
1788
|
+
}
|
|
1789
|
+
const scope = portalRequest.scope;
|
|
1790
|
+
if (!scope) {
|
|
1791
|
+
ctx.throw(400, "Invalid portal scope");
|
|
1792
|
+
return;
|
|
1793
|
+
}
|
|
1794
|
+
await assertDesktopRouteMutationTargetsBelongToScope(ctx, scope);
|
|
1795
|
+
await assertDesktopRouteParentsBelongToScope(ctx, scope);
|
|
1796
|
+
(_b = ctx.action) == null ? void 0 : _b.mergeParams(
|
|
1797
|
+
{
|
|
1798
|
+
values: withoutDesktopRouteOwnerFields((_a = ctx.action) == null ? void 0 : _a.params.values)
|
|
1799
|
+
},
|
|
1800
|
+
{
|
|
1801
|
+
values: "overwrite"
|
|
1802
|
+
}
|
|
1803
|
+
);
|
|
1804
|
+
await next();
|
|
1805
|
+
}
|
|
1806
|
+
function getDesktopRoutePortalContext(context) {
|
|
1807
|
+
var _a;
|
|
1808
|
+
if (!isRecordLike(context)) {
|
|
1809
|
+
return;
|
|
1810
|
+
}
|
|
1811
|
+
const ctx = context;
|
|
1812
|
+
if (((_a = ctx.action) == null ? void 0 : _a.resourceName) !== "desktopRoutes" || !hasActionParam(ctx.action.params, "portal")) {
|
|
1813
|
+
return;
|
|
1814
|
+
}
|
|
1815
|
+
return ctx;
|
|
1816
|
+
}
|
|
1817
|
+
async function resolveRequiredDesktopRouteScope(ctx, transaction) {
|
|
1818
|
+
const portalRequest = await findRequestedMultiPortal(ctx, transaction);
|
|
1819
|
+
if (!portalRequest.scope) {
|
|
1820
|
+
ctx.throw(400, "Invalid portal scope");
|
|
1821
|
+
}
|
|
1822
|
+
return portalRequest.scope;
|
|
1823
|
+
}
|
|
1824
|
+
function withDesktopRouteScopeFilter(filter, scope) {
|
|
1825
|
+
return {
|
|
1826
|
+
...isRecordLike(filter) ? filter : {},
|
|
1827
|
+
...scope.filter
|
|
1828
|
+
};
|
|
1829
|
+
}
|
|
1830
|
+
class MultiPortalDesktopRouteRepository extends import_database.Repository {
|
|
1831
|
+
async withAuthoritativeScopeTransaction(existingTransaction, callback) {
|
|
1832
|
+
if (existingTransaction) {
|
|
1833
|
+
return callback(existingTransaction);
|
|
1834
|
+
}
|
|
1835
|
+
return this.database.sequelize.transaction(callback);
|
|
1836
|
+
}
|
|
1837
|
+
async create(options) {
|
|
1838
|
+
const ctx = getDesktopRoutePortalContext(options.context);
|
|
1839
|
+
if (!ctx) {
|
|
1840
|
+
return super.create(options);
|
|
1841
|
+
}
|
|
1842
|
+
return this.withAuthoritativeScopeTransaction(options.transaction, async (transaction) => {
|
|
1843
|
+
const scope = await resolveRequiredDesktopRouteScope(ctx, transaction);
|
|
1844
|
+
const values = withDesktopRouteEffectiveOwner(options.values, scope);
|
|
1845
|
+
await assertDesktopRouteParentsBelongToScope(ctx, scope, values, transaction);
|
|
1846
|
+
return super.create({
|
|
1847
|
+
...options,
|
|
1848
|
+
values,
|
|
1849
|
+
transaction
|
|
1850
|
+
});
|
|
1851
|
+
});
|
|
1852
|
+
}
|
|
1853
|
+
async update(options) {
|
|
1854
|
+
const ctx = getDesktopRoutePortalContext(options.context);
|
|
1855
|
+
if (!ctx) {
|
|
1856
|
+
return super.update(options);
|
|
1857
|
+
}
|
|
1858
|
+
return this.withAuthoritativeScopeTransaction(options.transaction, async (transaction) => {
|
|
1859
|
+
const scope = await resolveRequiredDesktopRouteScope(ctx, transaction);
|
|
1860
|
+
await assertDesktopRouteMutationTargetsBelongToScope(ctx, scope, transaction, options);
|
|
1861
|
+
const values = withoutDesktopRouteOwnerFields(options.values);
|
|
1862
|
+
await assertDesktopRouteParentsBelongToScope(ctx, scope, values, transaction);
|
|
1863
|
+
return super.update({
|
|
1864
|
+
...options,
|
|
1865
|
+
filter: withDesktopRouteScopeFilter(options.filter, scope),
|
|
1866
|
+
values,
|
|
1867
|
+
transaction
|
|
1868
|
+
});
|
|
1869
|
+
});
|
|
1870
|
+
}
|
|
1871
|
+
async updateOrCreate(options) {
|
|
1872
|
+
const ctx = getDesktopRoutePortalContext(options.context);
|
|
1873
|
+
if (!ctx) {
|
|
1874
|
+
return super.updateOrCreate(options);
|
|
1875
|
+
}
|
|
1876
|
+
return this.withAuthoritativeScopeTransaction(options.transaction, async (transaction) => {
|
|
1877
|
+
const scope = await resolveRequiredDesktopRouteScope(ctx, transaction);
|
|
1878
|
+
const values = withDesktopRouteEffectiveOwner(options.values, scope);
|
|
1879
|
+
await assertDesktopRouteParentsBelongToScope(ctx, scope, values, transaction);
|
|
1880
|
+
await assertDesktopRouteUpsertMatchesBelongToScope(ctx, scope, values, options.filterKeys, transaction);
|
|
1881
|
+
return super.updateOrCreate({
|
|
1882
|
+
...options,
|
|
1883
|
+
values,
|
|
1884
|
+
transaction
|
|
1885
|
+
});
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
async function guardDesktopRouteMoveScope(ctx, next) {
|
|
1890
|
+
var _a;
|
|
1891
|
+
const portalRequest = await findRequestedMultiPortal(ctx);
|
|
1892
|
+
if (!portalRequest.requested) {
|
|
1893
|
+
await next();
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
const params = ((_a = ctx.action) == null ? void 0 : _a.params) ?? {};
|
|
1897
|
+
const sourceId = params.sourceId;
|
|
1898
|
+
const targetId = params.targetId;
|
|
1899
|
+
const hasTargetId = targetId !== void 0 && targetId !== null;
|
|
1900
|
+
if (typeof sourceId !== "string" && typeof sourceId !== "number" || hasTargetId && typeof targetId !== "string" && typeof targetId !== "number" || params.sortField !== void 0 && params.sortField !== "sort") {
|
|
1901
|
+
ctx.throw(400, "Invalid desktop route move scope");
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
await ctx.db.sequelize.transaction(async (transaction) => {
|
|
1905
|
+
const scope = await resolveRequiredDesktopRouteScope(ctx, transaction);
|
|
1906
|
+
const routeIds = uniqueDesktopRouteIds(hasTargetId ? [sourceId, targetId] : [sourceId]);
|
|
1907
|
+
const allRoutes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1908
|
+
fields: ["id", "parentId", "sort"],
|
|
1909
|
+
filter: {
|
|
1910
|
+
id: routeIds
|
|
1911
|
+
},
|
|
1912
|
+
lock: transaction.LOCK.UPDATE,
|
|
1913
|
+
transaction
|
|
1914
|
+
});
|
|
1915
|
+
const scopedRoutes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1916
|
+
fields: ["id", "parentId", "sort"],
|
|
1917
|
+
filter: {
|
|
1918
|
+
id: routeIds,
|
|
1919
|
+
...scope.filter
|
|
1920
|
+
},
|
|
1921
|
+
transaction
|
|
1922
|
+
});
|
|
1923
|
+
if (normalizeDesktopRouteTargetIds(allRoutes).join(",") !== normalizeDesktopRouteTargetIds(scopedRoutes).join(",") || scopedRoutes.length !== routeIds.length) {
|
|
1924
|
+
ctx.throw(400, "Move target does not belong to the requested portal scope");
|
|
1925
|
+
return;
|
|
1926
|
+
}
|
|
1927
|
+
const routesById = new Map(
|
|
1928
|
+
scopedRoutes.map((route) => [String(route.get("id")), route])
|
|
1929
|
+
);
|
|
1930
|
+
let source = routesById.get(String(sourceId));
|
|
1931
|
+
const target = hasTargetId ? routesById.get(String(targetId)) : void 0;
|
|
1932
|
+
if (!source || hasTargetId && !target) {
|
|
1933
|
+
ctx.throw(400, "Move target does not belong to the requested portal scope");
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
const targetScope = isRecordLike(params.targetScope) ? params.targetScope : void 0;
|
|
1937
|
+
const requestedParentId = targetScope == null ? void 0 : targetScope.parentId;
|
|
1938
|
+
const parentIds = uniqueDesktopRouteIds(
|
|
1939
|
+
[source.get("parentId"), target == null ? void 0 : target.get("parentId"), requestedParentId].filter(
|
|
1940
|
+
(id) => typeof id === "string" || typeof id === "number"
|
|
1941
|
+
)
|
|
1942
|
+
);
|
|
1943
|
+
if (parentIds.length) {
|
|
1944
|
+
const parents = await ctx.db.getRepository("desktopRoutes").find({
|
|
1945
|
+
fields: ["id"],
|
|
1946
|
+
filter: {
|
|
1947
|
+
id: parentIds,
|
|
1948
|
+
...scope.filter
|
|
1949
|
+
},
|
|
1950
|
+
transaction
|
|
1951
|
+
});
|
|
1952
|
+
if (normalizeDesktopRouteTargetIds(parents).join(",") !== parentIds.map(String).sort().join(",")) {
|
|
1953
|
+
ctx.throw(400, "Move parent does not belong to the requested portal scope");
|
|
1954
|
+
return;
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
const updateSource = async (values, silent) => {
|
|
1958
|
+
const [updatedSource] = await ctx.db.getRepository("desktopRoutes").update({
|
|
1959
|
+
filterByTk: sourceId,
|
|
1960
|
+
values,
|
|
1961
|
+
silent,
|
|
1962
|
+
transaction
|
|
1963
|
+
});
|
|
1964
|
+
if (!updatedSource) {
|
|
1965
|
+
ctx.throw(400, "Move source does not belong to the requested portal scope");
|
|
1966
|
+
}
|
|
1967
|
+
source = updatedSource;
|
|
1968
|
+
};
|
|
1969
|
+
if (target) {
|
|
1970
|
+
const targetParentId = target.get("parentId");
|
|
1971
|
+
if (source.get("parentId") !== targetParentId) {
|
|
1972
|
+
await updateSource({ parentId: targetParentId }, false);
|
|
1973
|
+
}
|
|
1974
|
+
const sourceSort = source.get("sort");
|
|
1975
|
+
let targetSort = target.get("sort");
|
|
1976
|
+
if (typeof sourceSort !== "number" || typeof targetSort !== "number") {
|
|
1977
|
+
ctx.throw(400, "Invalid desktop route sort value");
|
|
1978
|
+
return;
|
|
1979
|
+
}
|
|
1980
|
+
if (params.method === "insertAfter") {
|
|
1981
|
+
targetSort += 1;
|
|
1982
|
+
}
|
|
1983
|
+
const movesForward = targetSort > sourceSort;
|
|
1984
|
+
const affectedRoutes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1985
|
+
fields: ["id"],
|
|
1986
|
+
filter: {
|
|
1987
|
+
...scope.filter,
|
|
1988
|
+
parentId: targetParentId ?? null,
|
|
1989
|
+
sort: movesForward ? {
|
|
1990
|
+
$gt: sourceSort,
|
|
1991
|
+
$lte: targetSort
|
|
1992
|
+
} : {
|
|
1993
|
+
$lt: sourceSort,
|
|
1994
|
+
$gte: targetSort
|
|
1995
|
+
}
|
|
1996
|
+
},
|
|
1997
|
+
transaction
|
|
1998
|
+
});
|
|
1999
|
+
const affectedRouteIds = affectedRoutes.map((route) => route.get("id"));
|
|
2000
|
+
if (affectedRouteIds.length) {
|
|
2001
|
+
await ctx.db.getCollection("desktopRoutes").model.increment("sort", {
|
|
2002
|
+
where: {
|
|
2003
|
+
id: {
|
|
2004
|
+
[import_database.Op.in]: affectedRouteIds
|
|
2005
|
+
}
|
|
2006
|
+
},
|
|
2007
|
+
by: movesForward ? -1 : 1,
|
|
2008
|
+
silent: true,
|
|
2009
|
+
transaction
|
|
2010
|
+
});
|
|
2011
|
+
}
|
|
2012
|
+
await updateSource({ sort: targetSort }, true);
|
|
2013
|
+
}
|
|
2014
|
+
if (requestedParentId && source.get("parentId") !== requestedParentId) {
|
|
2015
|
+
await updateSource({ parentId: requestedParentId }, false);
|
|
2016
|
+
if (params.method === "prepend") {
|
|
2017
|
+
await updateSource({ sort: 0 }, true);
|
|
2018
|
+
}
|
|
2019
|
+
}
|
|
2020
|
+
if (params.sticky) {
|
|
2021
|
+
await updateSource({ sort: 0 }, true);
|
|
2022
|
+
}
|
|
2023
|
+
});
|
|
2024
|
+
ctx.body = "ok";
|
|
2025
|
+
}
|
|
2026
|
+
function normalizeDesktopRouteFilterByTk(filterByTk) {
|
|
2027
|
+
const values = Array.isArray(filterByTk) ? filterByTk : [filterByTk];
|
|
2028
|
+
return uniqueDesktopRouteIds(
|
|
2029
|
+
values.filter((value) => typeof value === "string" || typeof value === "number")
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
async function getDesktopRouteDestroyOwnerScope(ctx, transaction) {
|
|
2033
|
+
var _a, _b, _c, _d;
|
|
2034
|
+
const hasPortalScope = hasActionParam((_a = ctx.action) == null ? void 0 : _a.params, "portal");
|
|
2035
|
+
const hasLayoutScope = hasActionParam((_b = ctx.action) == null ? void 0 : _b.params, "layout");
|
|
2036
|
+
const portalUid = getExplicitRequestedLayoutUid((_c = ctx.action) == null ? void 0 : _c.params.portal);
|
|
2037
|
+
const layoutUid = getExplicitRequestedLayoutUid((_d = ctx.action) == null ? void 0 : _d.params.layout);
|
|
2038
|
+
if (hasPortalScope && hasLayoutScope) {
|
|
2039
|
+
ctx.throw(400, "layout and portal cannot be used together");
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
if (hasPortalScope) {
|
|
2043
|
+
if (!portalUid) {
|
|
2044
|
+
ctx.throw(400, "Invalid portal scope");
|
|
2045
|
+
return;
|
|
2046
|
+
}
|
|
2047
|
+
const portalRequest = await findRequestedMultiPortal(ctx, transaction);
|
|
2048
|
+
if (!portalRequest.scope) {
|
|
2049
|
+
ctx.throw(400, "Invalid portal scope");
|
|
2050
|
+
return;
|
|
2051
|
+
}
|
|
2052
|
+
return portalRequest.scope;
|
|
2053
|
+
}
|
|
2054
|
+
if (!hasLayoutScope) {
|
|
2055
|
+
return;
|
|
2056
|
+
}
|
|
2057
|
+
if (!layoutUid) {
|
|
2058
|
+
ctx.throw(400, "Invalid layout scope");
|
|
2059
|
+
return;
|
|
2060
|
+
}
|
|
2061
|
+
const layout = await ctx.db.getRepository("uiLayouts").findOne({
|
|
2062
|
+
fields: ["uid"],
|
|
2063
|
+
filter: {
|
|
2064
|
+
uid: layoutUid
|
|
2065
|
+
},
|
|
2066
|
+
transaction
|
|
2067
|
+
});
|
|
2068
|
+
if (!layout) {
|
|
2069
|
+
ctx.throw(400, "Invalid layout scope");
|
|
2070
|
+
return;
|
|
2071
|
+
}
|
|
2072
|
+
return {
|
|
2073
|
+
filter: {
|
|
2074
|
+
"uiLayouts.uid": layoutUid
|
|
2075
|
+
},
|
|
2076
|
+
relation: "uiLayouts",
|
|
2077
|
+
uid: layoutUid
|
|
2078
|
+
};
|
|
2079
|
+
}
|
|
2080
|
+
async function findDesktopRouteDestroyRoots(ctx, transaction, scopeFilter) {
|
|
2081
|
+
var _a, _b, _c;
|
|
2082
|
+
const filterByTk = normalizeDesktopRouteFilterByTk((_a = ctx.action) == null ? void 0 : _a.params.filterByTk);
|
|
2083
|
+
const actionFilter = isRecordLike((_b = ctx.action) == null ? void 0 : _b.params.filter) ? (_c = ctx.action) == null ? void 0 : _c.params.filter : void 0;
|
|
2084
|
+
if (!filterByTk.length && !actionFilter) {
|
|
2085
|
+
return [];
|
|
2086
|
+
}
|
|
2087
|
+
const records = await ctx.db.getRepository("desktopRoutes").find({
|
|
2088
|
+
fields: ["id"],
|
|
2089
|
+
filter: {
|
|
2090
|
+
...actionFilter ?? {},
|
|
2091
|
+
...scopeFilter ?? {},
|
|
2092
|
+
...filterByTk.length ? { id: filterByTk } : {}
|
|
2093
|
+
},
|
|
2094
|
+
...scopeFilter ? {} : { lock: transaction.LOCK.UPDATE },
|
|
2095
|
+
transaction
|
|
2096
|
+
});
|
|
2097
|
+
return collectDesktopRouteIds(records);
|
|
2098
|
+
}
|
|
2099
|
+
async function findDesktopRouteSubtreeIds(ctx, rootId, transaction) {
|
|
2100
|
+
const rootRoutes = await ctx.db.getRepository("desktopRoutes").find({
|
|
2101
|
+
fields: ["id"],
|
|
2102
|
+
filter: {
|
|
2103
|
+
id: rootId
|
|
2104
|
+
},
|
|
2105
|
+
lock: transaction.LOCK.UPDATE,
|
|
2106
|
+
transaction
|
|
2107
|
+
});
|
|
2108
|
+
const routeIds = collectDesktopRouteIds(rootRoutes);
|
|
2109
|
+
let parentIds = [...routeIds];
|
|
2110
|
+
while (parentIds.length) {
|
|
2111
|
+
const children = await ctx.db.getRepository("desktopRoutes").find({
|
|
2112
|
+
fields: ["id"],
|
|
2113
|
+
filter: {
|
|
2114
|
+
parentId: parentIds
|
|
2115
|
+
},
|
|
2116
|
+
lock: transaction.LOCK.UPDATE,
|
|
2117
|
+
sort: ["id"],
|
|
2118
|
+
transaction
|
|
2119
|
+
});
|
|
2120
|
+
const childIds = collectDesktopRouteIds(children).filter((routeId) => !routeIds.includes(routeId));
|
|
2121
|
+
routeIds.push(...childIds);
|
|
2122
|
+
parentIds = childIds;
|
|
2123
|
+
}
|
|
2124
|
+
return routeIds;
|
|
2125
|
+
}
|
|
2126
|
+
function getDesktopRouteOwnerUids(route, relation) {
|
|
2127
|
+
const owners = getRecordField(route, relation);
|
|
2128
|
+
if (!Array.isArray(owners)) {
|
|
2129
|
+
return [];
|
|
2130
|
+
}
|
|
2131
|
+
return owners.map((owner) => getRecordField(owner, "uid")).filter((uid) => typeof uid === "string" && !!uid);
|
|
2132
|
+
}
|
|
2133
|
+
function desktopRouteHasOwnerOutsideScope(route, scope) {
|
|
2134
|
+
const multiPortalUids = getDesktopRouteOwnerUids(route, "multiPortals");
|
|
2135
|
+
const uiLayoutUids = getDesktopRouteOwnerUids(route, "uiLayouts");
|
|
2136
|
+
if (scope.relation === "multiPortals") {
|
|
2137
|
+
return multiPortalUids.some((uid) => uid !== scope.uid) || uiLayoutUids.length > 0;
|
|
2138
|
+
}
|
|
2139
|
+
return uiLayoutUids.some((uid) => uid !== scope.uid) || multiPortalUids.length > 0;
|
|
2140
|
+
}
|
|
2141
|
+
async function detachDesktopRouteTreeOwner(ctx, routeIds, scope, transaction) {
|
|
2142
|
+
if (scope.relation === "multiPortals") {
|
|
2143
|
+
await ctx.db.getRepository("rolesMultiPortalDesktopRoutes").destroy({
|
|
2144
|
+
filter: {
|
|
2145
|
+
multiPortalUid: scope.uid,
|
|
2146
|
+
desktopRouteId: routeIds
|
|
2147
|
+
},
|
|
2148
|
+
transaction
|
|
2149
|
+
});
|
|
2150
|
+
}
|
|
2151
|
+
for (const routeId of routeIds) {
|
|
2152
|
+
await ctx.db.getRepository(`desktopRoutes.${scope.relation}`, routeId).remove({
|
|
2153
|
+
tk: scope.uid,
|
|
2154
|
+
transaction
|
|
2155
|
+
});
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
async function detachOwnerAndFindDestroyRoots(ctx, routeIds, scope, transaction) {
|
|
2159
|
+
var _a;
|
|
2160
|
+
await ctx.db.getRepository("desktopRoutes").find({
|
|
2161
|
+
fields: ["id"],
|
|
2162
|
+
filter: {
|
|
2163
|
+
id: routeIds
|
|
2164
|
+
},
|
|
2165
|
+
lock: transaction.LOCK.UPDATE,
|
|
2166
|
+
transaction
|
|
2167
|
+
});
|
|
2168
|
+
const routes = await ctx.db.getRepository("desktopRoutes").find({
|
|
2169
|
+
fields: ["id", "parentId"],
|
|
2170
|
+
appends: ["multiPortals", "uiLayouts"],
|
|
2171
|
+
filter: {
|
|
2172
|
+
id: routeIds
|
|
2173
|
+
},
|
|
2174
|
+
transaction
|
|
2175
|
+
});
|
|
2176
|
+
const routesById = new Map(routes.map((route) => [String(route.get("id")), route]));
|
|
2177
|
+
const ownerlessRouteIds = new Set(
|
|
2178
|
+
routes.filter((route) => !desktopRouteHasOwnerOutsideScope(route, scope)).map((route) => String(route.get("id")))
|
|
2179
|
+
);
|
|
2180
|
+
await detachDesktopRouteTreeOwner(ctx, routeIds, scope, transaction);
|
|
2181
|
+
for (const route of routes) {
|
|
2182
|
+
if (ownerlessRouteIds.has(String(route.get("id")))) {
|
|
2183
|
+
continue;
|
|
2184
|
+
}
|
|
2185
|
+
let parentId = route.get("parentId");
|
|
2186
|
+
const visitedParentIds = /* @__PURE__ */ new Set();
|
|
2187
|
+
while (parentId !== null && parentId !== void 0 && ownerlessRouteIds.has(String(parentId))) {
|
|
2188
|
+
const normalizedParentId = String(parentId);
|
|
2189
|
+
if (visitedParentIds.has(normalizedParentId)) {
|
|
2190
|
+
ctx.throw(400, "Invalid desktop route tree");
|
|
2191
|
+
return [];
|
|
2192
|
+
}
|
|
2193
|
+
visitedParentIds.add(normalizedParentId);
|
|
2194
|
+
parentId = ((_a = routesById.get(normalizedParentId)) == null ? void 0 : _a.get("parentId")) ?? null;
|
|
2195
|
+
}
|
|
2196
|
+
if (route.get("parentId") !== parentId) {
|
|
2197
|
+
await ctx.db.getRepository("desktopRoutes").update({
|
|
2198
|
+
filterByTk: route.get("id"),
|
|
2199
|
+
values: {
|
|
2200
|
+
parentId
|
|
2201
|
+
},
|
|
2202
|
+
transaction
|
|
2203
|
+
});
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
return routes.filter((route) => {
|
|
2207
|
+
if (!ownerlessRouteIds.has(String(route.get("id")))) {
|
|
2208
|
+
return false;
|
|
2209
|
+
}
|
|
2210
|
+
const parentId = route.get("parentId");
|
|
2211
|
+
return parentId === null || parentId === void 0 || !ownerlessRouteIds.has(String(parentId));
|
|
2212
|
+
}).map((route) => route.get("id"));
|
|
2213
|
+
}
|
|
2214
|
+
async function destroyScopedDesktopRoutes(ctx, next) {
|
|
2215
|
+
var _a, _b;
|
|
2216
|
+
if (!hasActionParam((_a = ctx.action) == null ? void 0 : _a.params, "portal") && !hasActionParam((_b = ctx.action) == null ? void 0 : _b.params, "layout")) {
|
|
2217
|
+
await next();
|
|
2218
|
+
return;
|
|
2219
|
+
}
|
|
2220
|
+
const result = await ctx.db.sequelize.transaction(async (transaction) => {
|
|
2221
|
+
const scope = await getDesktopRouteDestroyOwnerScope(ctx, transaction);
|
|
2222
|
+
if (!scope) {
|
|
2223
|
+
return true;
|
|
2224
|
+
}
|
|
2225
|
+
const allRootIds = (await findDesktopRouteDestroyRoots(ctx, transaction)).sort(
|
|
2226
|
+
(left, right) => String(left).localeCompare(String(right))
|
|
2227
|
+
);
|
|
2228
|
+
const rootIds = (await findDesktopRouteDestroyRoots(ctx, transaction, scope.filter)).sort(
|
|
2229
|
+
(left, right) => String(left).localeCompare(String(right))
|
|
2230
|
+
);
|
|
2231
|
+
if (allRootIds.map(String).join(",") !== rootIds.map(String).join(",")) {
|
|
2232
|
+
ctx.throw(400, "Desktop route does not belong to the requested portal scope");
|
|
2233
|
+
return true;
|
|
2234
|
+
}
|
|
2235
|
+
const routeTrees = [];
|
|
2236
|
+
for (const rootId of rootIds) {
|
|
2237
|
+
routeTrees.push({
|
|
2238
|
+
rootId,
|
|
2239
|
+
routeIds: await findDesktopRouteSubtreeIds(ctx, rootId, transaction)
|
|
2240
|
+
});
|
|
2241
|
+
}
|
|
2242
|
+
const rootRouteTrees = routeTrees.filter(
|
|
2243
|
+
(routeTree, index) => !routeTrees.some(
|
|
2244
|
+
(candidate, candidateIndex) => candidateIndex !== index && candidate.routeIds.includes(routeTree.rootId)
|
|
2245
|
+
)
|
|
2246
|
+
);
|
|
2247
|
+
const physicalDestroyRootIds = [];
|
|
2248
|
+
for (const routeTree of rootRouteTrees) {
|
|
2249
|
+
physicalDestroyRootIds.push(
|
|
2250
|
+
...await detachOwnerAndFindDestroyRoots(ctx, routeTree.routeIds, scope, transaction)
|
|
2251
|
+
);
|
|
2252
|
+
}
|
|
2253
|
+
if (!physicalDestroyRootIds.length) {
|
|
2254
|
+
return true;
|
|
2255
|
+
}
|
|
2256
|
+
return ctx.db.getRepository("desktopRoutes").destroy({
|
|
2257
|
+
context: ctx,
|
|
2258
|
+
filterByTk: physicalDestroyRootIds.length === 1 ? physicalDestroyRootIds[0] : physicalDestroyRootIds,
|
|
2259
|
+
transaction
|
|
2260
|
+
});
|
|
2261
|
+
});
|
|
2262
|
+
ctx.status = 200;
|
|
2263
|
+
ctx.body = result;
|
|
2264
|
+
}
|
|
1479
2265
|
async function listEnabledMultiPortals(ctx, next) {
|
|
1480
2266
|
const records = await ctx.db.getRepository("multiPortals").find({
|
|
1481
|
-
filter:
|
|
2267
|
+
filter: {
|
|
1482
2268
|
enabled: true,
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
2269
|
+
uiLayoutUid: {
|
|
2270
|
+
$in: [...import_constants.MULTI_PORTAL_UI_LAYOUT_UIDS]
|
|
2271
|
+
}
|
|
2272
|
+
},
|
|
2273
|
+
fields: [...MULTI_PORTAL_RUNTIME_FIELDS],
|
|
1487
2274
|
sort: ["uid"]
|
|
1488
2275
|
});
|
|
1489
2276
|
ctx.body = records.map((record) => pickMultiPortalRuntimeFields(record));
|
|
1490
2277
|
await next();
|
|
1491
2278
|
}
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
return;
|
|
2279
|
+
const DEFAULT_MULTI_PORTAL_RESPONSE_FIELDS = ["uid", "portalType", "routePath"];
|
|
2280
|
+
function getDefaultMultiPortalType(record) {
|
|
2281
|
+
const portalType = record.get("portalType");
|
|
2282
|
+
if (portalType === null || portalType === void 0) {
|
|
2283
|
+
return "no-code";
|
|
1498
2284
|
}
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
2285
|
+
return isMultiPortalSeedType(portalType) ? portalType : null;
|
|
2286
|
+
}
|
|
2287
|
+
function pickDefaultMultiPortalFields(record) {
|
|
2288
|
+
return {
|
|
2289
|
+
uid: String(record.get("uid")),
|
|
2290
|
+
portalType: getDefaultMultiPortalType(record) || "no-code",
|
|
2291
|
+
routePath: String(record.get("routePath"))
|
|
1502
2292
|
};
|
|
1503
|
-
|
|
1504
|
-
|
|
2293
|
+
}
|
|
2294
|
+
async function findEnabledDefaultMultiPortal(ctx, transaction) {
|
|
2295
|
+
const record = await ctx.db.getRepository("multiPortals").findOne({
|
|
2296
|
+
filter: {
|
|
2297
|
+
enabled: true,
|
|
2298
|
+
isDefault: true
|
|
2299
|
+
},
|
|
2300
|
+
fields: [...DEFAULT_MULTI_PORTAL_RESPONSE_FIELDS, "uiLayoutUid"],
|
|
2301
|
+
transaction
|
|
2302
|
+
});
|
|
2303
|
+
if (!record) {
|
|
2304
|
+
return null;
|
|
2305
|
+
}
|
|
2306
|
+
const portalType = getDefaultMultiPortalType(record);
|
|
2307
|
+
if (!portalType) {
|
|
2308
|
+
return null;
|
|
2309
|
+
}
|
|
2310
|
+
if (!(0, import_constants.isMultiPortalUiLayoutUid)(record.get("uiLayoutUid"))) {
|
|
2311
|
+
return null;
|
|
2312
|
+
}
|
|
2313
|
+
return record;
|
|
2314
|
+
}
|
|
2315
|
+
async function getDefaultMultiPortal(ctx, next) {
|
|
2316
|
+
const record = await findEnabledDefaultMultiPortal(ctx);
|
|
2317
|
+
ctx.body = record ? pickDefaultMultiPortalFields(record) : null;
|
|
2318
|
+
ctx.status = 200;
|
|
2319
|
+
await next();
|
|
2320
|
+
}
|
|
2321
|
+
async function setDefaultMultiPortal(ctx, next) {
|
|
2322
|
+
var _a;
|
|
2323
|
+
const filterByTk = (_a = ctx.action) == null ? void 0 : _a.params.filterByTk;
|
|
2324
|
+
if (filterByTk === void 0 || filterByTk === null || filterByTk === "") {
|
|
2325
|
+
ctx.throw(400, ctx.t("filterByTk is required", { ns: import_constants.NAMESPACE }));
|
|
2326
|
+
return;
|
|
1505
2327
|
}
|
|
2328
|
+
const repository = ctx.db.getRepository("multiPortals");
|
|
2329
|
+
const updated = await ctx.db.sequelize.transaction(async (transaction) => {
|
|
2330
|
+
const target = await repository.findOne({
|
|
2331
|
+
filterByTk,
|
|
2332
|
+
fields: [...DEFAULT_MULTI_PORTAL_RESPONSE_FIELDS, "enabled", "uiLayoutUid"],
|
|
2333
|
+
lock: transaction.LOCK.UPDATE,
|
|
2334
|
+
transaction
|
|
2335
|
+
});
|
|
2336
|
+
if (!target) {
|
|
2337
|
+
ctx.throw(404, ctx.t("Portal not found", { ns: import_constants.NAMESPACE }));
|
|
2338
|
+
return null;
|
|
2339
|
+
}
|
|
2340
|
+
if (target.get("enabled") !== true) {
|
|
2341
|
+
ctx.throw(400, ctx.t("Disabled Portal cannot be set as default", { ns: import_constants.NAMESPACE }));
|
|
2342
|
+
return null;
|
|
2343
|
+
}
|
|
2344
|
+
const portalType = getDefaultMultiPortalType(target);
|
|
2345
|
+
if (!portalType) {
|
|
2346
|
+
ctx.throw(400, ctx.t("Unsupported Portal type cannot be set as default", { ns: import_constants.NAMESPACE }));
|
|
2347
|
+
return null;
|
|
2348
|
+
}
|
|
2349
|
+
if (!(0, import_constants.isMultiPortalUiLayoutUid)(target.get("uiLayoutUid"))) {
|
|
2350
|
+
ctx.throw(400, ctx.t("Portal device configuration is invalid", { ns: import_constants.NAMESPACE }));
|
|
2351
|
+
return null;
|
|
2352
|
+
}
|
|
2353
|
+
await repository.update({
|
|
2354
|
+
filter: { isDefault: true },
|
|
2355
|
+
values: { isDefault: null },
|
|
2356
|
+
transaction
|
|
2357
|
+
});
|
|
2358
|
+
await repository.update({
|
|
2359
|
+
filterByTk,
|
|
2360
|
+
values: { isDefault: true },
|
|
2361
|
+
transaction
|
|
2362
|
+
});
|
|
2363
|
+
return repository.findOne({
|
|
2364
|
+
filterByTk,
|
|
2365
|
+
fields: [...DEFAULT_MULTI_PORTAL_RESPONSE_FIELDS],
|
|
2366
|
+
transaction
|
|
2367
|
+
});
|
|
2368
|
+
});
|
|
2369
|
+
ctx.body = updated ? pickDefaultMultiPortalFields(updated) : null;
|
|
2370
|
+
await next();
|
|
2371
|
+
}
|
|
2372
|
+
async function listAccessibleMultiPortals(ctx, next) {
|
|
2373
|
+
const accessiblePortalUids = await listCurrentRoleAccessibleMultiPortalUids(ctx);
|
|
1506
2374
|
const records = await ctx.db.getRepository("multiPortals").find({
|
|
1507
|
-
filter:
|
|
1508
|
-
|
|
1509
|
-
|
|
2375
|
+
filter: {
|
|
2376
|
+
enabled: true,
|
|
2377
|
+
uiLayoutUid: {
|
|
2378
|
+
$in: [...import_constants.MULTI_PORTAL_UI_LAYOUT_UIDS]
|
|
2379
|
+
}
|
|
2380
|
+
},
|
|
2381
|
+
fields: [...MULTI_PORTAL_ACCESSIBLE_FIELDS],
|
|
1510
2382
|
sort: ["uid"]
|
|
1511
2383
|
});
|
|
1512
|
-
|
|
2384
|
+
const accessiblePortalUidSet = Array.isArray(accessiblePortalUids) ? new Set(accessiblePortalUids) : void 0;
|
|
2385
|
+
const accessibleRecords = records.filter(
|
|
2386
|
+
(record) => (0, import_constants.isDefaultLayoutMultiPortalUid)(record.get("uid")) || !accessiblePortalUidSet || accessiblePortalUidSet.has(String(record.get("uid")))
|
|
2387
|
+
);
|
|
2388
|
+
ctx.body = accessibleRecords.map((record) => pickMultiPortalAccessibleFields(record));
|
|
1513
2389
|
await next();
|
|
1514
2390
|
}
|
|
1515
|
-
async function grantDefaultRouteAccessToNewMultiPortalRoutes(ctx, multiPortalUid, desktopRouteIds) {
|
|
2391
|
+
async function grantDefaultRouteAccessToNewMultiPortalRoutes(ctx, multiPortalUid, desktopRouteIds, transaction) {
|
|
1516
2392
|
if (!desktopRouteIds.length) {
|
|
1517
2393
|
return;
|
|
1518
2394
|
}
|
|
1519
|
-
if (!ctx.db.getCollection("rolesMultiPortalRoutePolicies") || !ctx.db.getCollection("rolesMultiPortalDesktopRoutes")) {
|
|
1520
|
-
return;
|
|
1521
|
-
}
|
|
1522
2395
|
const routePolicies = await ctx.db.getRepository("rolesMultiPortalRoutePolicies").find({
|
|
1523
2396
|
fields: ["roleName"],
|
|
1524
2397
|
filter: {
|
|
1525
2398
|
multiPortalUid,
|
|
1526
2399
|
allowNewMenu: true
|
|
1527
|
-
}
|
|
2400
|
+
},
|
|
2401
|
+
transaction
|
|
1528
2402
|
});
|
|
1529
2403
|
const roleNames = routePolicies.map((routePolicy) => routePolicy.get("roleName")).filter((roleName) => typeof roleName === "string" && !!roleName);
|
|
1530
2404
|
if (!roleNames.length) {
|
|
@@ -1540,26 +2414,52 @@ async function grantDefaultRouteAccessToNewMultiPortalRoutes(ctx, multiPortalUid
|
|
|
1540
2414
|
multiPortalUid,
|
|
1541
2415
|
desktopRouteId
|
|
1542
2416
|
},
|
|
1543
|
-
context: ctx
|
|
2417
|
+
context: ctx,
|
|
2418
|
+
transaction
|
|
1544
2419
|
});
|
|
1545
2420
|
}
|
|
1546
2421
|
}
|
|
1547
2422
|
}
|
|
1548
|
-
async function
|
|
1549
|
-
if (!desktopRouteIds.length
|
|
1550
|
-
return;
|
|
1551
|
-
}
|
|
1552
|
-
const portalOnlyRouteIds = await getPortalOnlyDesktopRouteIds(ctx, desktopRouteIds);
|
|
1553
|
-
if (portalOnlyRouteIds.size === 0) {
|
|
2423
|
+
async function removeLayoutRoutePermissionsFromPortalRoutes(ctx, desktopRouteIds, transaction) {
|
|
2424
|
+
if (!desktopRouteIds.length) {
|
|
1554
2425
|
return;
|
|
1555
2426
|
}
|
|
1556
2427
|
await ctx.db.getRepository("rolesDesktopRoutes").destroy({
|
|
1557
2428
|
filter: {
|
|
1558
|
-
desktopRouteId:
|
|
2429
|
+
desktopRouteId: desktopRouteIds
|
|
1559
2430
|
},
|
|
1560
|
-
context: ctx
|
|
2431
|
+
context: ctx,
|
|
2432
|
+
transaction
|
|
1561
2433
|
});
|
|
1562
2434
|
}
|
|
2435
|
+
async function reconcileCreatedDesktopRoutePermissions(desktopRoute, options) {
|
|
2436
|
+
const ctx = options == null ? void 0 : options.context;
|
|
2437
|
+
if (!ctx) {
|
|
2438
|
+
return;
|
|
2439
|
+
}
|
|
2440
|
+
const portalRequest = await findRequestedMultiPortal(ctx, options.transaction);
|
|
2441
|
+
if (!portalRequest.requested) {
|
|
2442
|
+
return;
|
|
2443
|
+
}
|
|
2444
|
+
const scope = portalRequest.scope;
|
|
2445
|
+
if (!scope) {
|
|
2446
|
+
ctx.throw(400, "Invalid portal scope");
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
2449
|
+
if (scope.relation !== "multiPortals") {
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
const transaction = options.transaction;
|
|
2453
|
+
if (!transaction) {
|
|
2454
|
+
throw new Error("Portal desktop route creation requires a transaction");
|
|
2455
|
+
}
|
|
2456
|
+
const desktopRouteId = desktopRoute.get("id");
|
|
2457
|
+
if (typeof desktopRouteId !== "string" && typeof desktopRouteId !== "number") {
|
|
2458
|
+
throw new Error("Created Portal desktop route has no identifier");
|
|
2459
|
+
}
|
|
2460
|
+
await removeLayoutRoutePermissionsFromPortalRoutes(ctx, [desktopRouteId], transaction);
|
|
2461
|
+
await grantDefaultRouteAccessToNewMultiPortalRoutes(ctx, scope.portalUid, [desktopRouteId], transaction);
|
|
2462
|
+
}
|
|
1563
2463
|
async function grantDefaultAccessToNewMultiPortal(db, multiPortal, options) {
|
|
1564
2464
|
if (multiPortal.get("enabled") !== true) {
|
|
1565
2465
|
return;
|
|
@@ -1568,7 +2468,7 @@ async function grantDefaultAccessToNewMultiPortal(db, multiPortal, options) {
|
|
|
1568
2468
|
if (typeof multiPortalUid !== "string" || !multiPortalUid) {
|
|
1569
2469
|
return;
|
|
1570
2470
|
}
|
|
1571
|
-
if (
|
|
2471
|
+
if ((0, import_constants.isDefaultLayoutMultiPortalUid)(multiPortalUid)) {
|
|
1572
2472
|
return;
|
|
1573
2473
|
}
|
|
1574
2474
|
if (!db.getCollection("roles") || !db.getCollection("rolesMultiPortals")) {
|
|
@@ -1602,7 +2502,7 @@ async function grantDefaultAccessToNewMultiPortal(db, multiPortal, options) {
|
|
|
1602
2502
|
transaction: options == null ? void 0 : options.transaction
|
|
1603
2503
|
});
|
|
1604
2504
|
}
|
|
1605
|
-
if (!db.getCollection("rolesMultiPortalRoutePolicies")) {
|
|
2505
|
+
if (multiPortal.get("portalType") === "ai" || !db.getCollection("rolesMultiPortalRoutePolicies")) {
|
|
1606
2506
|
return;
|
|
1607
2507
|
}
|
|
1608
2508
|
const routePolicyRepository = db.getRepository("rolesMultiPortalRoutePolicies");
|
|
@@ -1617,11 +2517,15 @@ async function grantDefaultAccessToNewMultiPortal(db, multiPortal, options) {
|
|
|
1617
2517
|
class PluginMultiPortalServer extends import_server.Plugin {
|
|
1618
2518
|
portalStorageTaskKeys = /* @__PURE__ */ new Set();
|
|
1619
2519
|
portalStorageTasks = /* @__PURE__ */ new Map();
|
|
2520
|
+
deprecatedInitPortalEnvWarningEmitted = false;
|
|
1620
2521
|
async afterAdd() {
|
|
1621
2522
|
}
|
|
1622
2523
|
getAppName() {
|
|
1623
2524
|
return this.app.name || MAIN_APP_NAME;
|
|
1624
2525
|
}
|
|
2526
|
+
getCurrentStorageAppName() {
|
|
2527
|
+
return normalizePortalStorageName(this.getAppName()) || MAIN_APP_NAME;
|
|
2528
|
+
}
|
|
1625
2529
|
getMultiPortalStorageItem(multiPortal, previous = false) {
|
|
1626
2530
|
const record = multiPortal;
|
|
1627
2531
|
const readField = (field) => previous && typeof record.previous === "function" ? record.previous(field) : getRecordField(record, field);
|
|
@@ -1636,8 +2540,7 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1636
2540
|
return {
|
|
1637
2541
|
appName: this.getAppName(),
|
|
1638
2542
|
portalName,
|
|
1639
|
-
enabled: readField("enabled") === true
|
|
1640
|
-
config: getPortalStorageConfig(readField("options"))
|
|
2543
|
+
enabled: readField("enabled") === true
|
|
1641
2544
|
};
|
|
1642
2545
|
}
|
|
1643
2546
|
warnPortalStorageSyncFailed(error) {
|
|
@@ -1647,6 +2550,14 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1647
2550
|
error
|
|
1648
2551
|
});
|
|
1649
2552
|
}
|
|
2553
|
+
logPortalBuildHtml(item, status, reason) {
|
|
2554
|
+
var _a, _b;
|
|
2555
|
+
(_b = (_a = this.app.logger) == null ? void 0 : _a.info) == null ? void 0 : _b.call(_a, `Portal yarn build:html ${status} for ${item.appName}/${item.portalName}`, {
|
|
2556
|
+
appName: item.appName,
|
|
2557
|
+
portalName: item.portalName,
|
|
2558
|
+
reason
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
1650
2561
|
getPortalStorageTaskKey(item) {
|
|
1651
2562
|
return `${item.appName}/${item.portalName}`;
|
|
1652
2563
|
}
|
|
@@ -1665,10 +2576,9 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1665
2576
|
}
|
|
1666
2577
|
await run();
|
|
1667
2578
|
}
|
|
1668
|
-
async
|
|
1669
|
-
await import_fs.default.promises.rm((0, import_utils.storagePathJoin)("portals", item.appName, item.portalName, "dist"), {
|
|
1670
|
-
force: true
|
|
1671
|
-
recursive: true
|
|
2579
|
+
async removePortalStorageIndexHtml(item) {
|
|
2580
|
+
await import_fs.default.promises.rm((0, import_utils.storagePathJoin)("portals", item.appName, item.portalName, "dist", "index.html"), {
|
|
2581
|
+
force: true
|
|
1672
2582
|
});
|
|
1673
2583
|
}
|
|
1674
2584
|
waitForPortalStorageTasks() {
|
|
@@ -1678,10 +2588,11 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1678
2588
|
}
|
|
1679
2589
|
return Promise.allSettled(tasks).then(() => void 0);
|
|
1680
2590
|
}
|
|
1681
|
-
async schedulePortalTemplateCopyAndBuild(item, template, portalDir) {
|
|
2591
|
+
async schedulePortalTemplateCopyAndBuild(item, template, portalDir, options) {
|
|
1682
2592
|
var _a;
|
|
1683
2593
|
const taskKey = this.getPortalStorageTaskKey(item);
|
|
1684
2594
|
if (this.portalStorageTaskKeys.has(taskKey)) {
|
|
2595
|
+
this.logPortalBuildHtml(item, "skipped", "a storage task is already running");
|
|
1685
2596
|
await ((_a = template.cleanup) == null ? void 0 : _a.call(template));
|
|
1686
2597
|
return;
|
|
1687
2598
|
}
|
|
@@ -1702,14 +2613,13 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1702
2613
|
await copyPortalTemplate(template.dir, portalDir);
|
|
1703
2614
|
await appendPortalStorageLog(logPath, `Default portal template copied to ${portalDir}.`);
|
|
1704
2615
|
}
|
|
1705
|
-
await ensurePortalStorageConfigFiles(portalDir, item);
|
|
1706
|
-
await appendPortalStorageLog(logPath, `Portal configuration files updated in ${portalDir}.`);
|
|
1707
2616
|
if (item.enabled) {
|
|
1708
|
-
|
|
2617
|
+
this.logPortalBuildHtml(item, "requested", "storage directory was initialized");
|
|
2618
|
+
await buildPortalStorageItem(portalDir, item, options);
|
|
2619
|
+
this.logPortalBuildHtml(item, "completed", "yarn build:html finished successfully");
|
|
1709
2620
|
return;
|
|
1710
2621
|
}
|
|
1711
|
-
|
|
1712
|
-
await appendPortalStorageLog(logPath, `Portal dist directory removed for ${item.appName}/${item.portalName}.`);
|
|
2622
|
+
this.logPortalBuildHtml(item, "skipped", "the portal is disabled");
|
|
1713
2623
|
} catch (error) {
|
|
1714
2624
|
await appendPortalStorageLog(
|
|
1715
2625
|
logPath,
|
|
@@ -1725,32 +2635,41 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1725
2635
|
this.portalStorageTasks.set(taskKey, task);
|
|
1726
2636
|
await task;
|
|
1727
2637
|
}
|
|
1728
|
-
async ensurePortalStorageItem(item) {
|
|
2638
|
+
async ensurePortalStorageItem(item, options = {}) {
|
|
1729
2639
|
const portalDir = (0, import_utils.storagePathJoin)("portals", item.appName, item.portalName);
|
|
1730
2640
|
const portalIndex = import_path.default.join(portalDir, "dist", "index.html");
|
|
2641
|
+
const logPath = getPortalStorageLogPath(item);
|
|
1731
2642
|
if (!await pathExists(portalDir)) {
|
|
1732
|
-
const template = await resolvePortalTemplate(getInitPortalTemplate(),
|
|
1733
|
-
await this.schedulePortalTemplateCopyAndBuild(item, template, portalDir);
|
|
2643
|
+
const template = await resolvePortalTemplate(getInitPortalTemplate(), logPath);
|
|
2644
|
+
await this.schedulePortalTemplateCopyAndBuild(item, template, portalDir, options);
|
|
1734
2645
|
return;
|
|
1735
2646
|
}
|
|
1736
|
-
await ensurePortalStorageConfigFiles(portalDir, item);
|
|
1737
2647
|
if (item.enabled) {
|
|
1738
|
-
|
|
1739
|
-
|
|
2648
|
+
const hasPortalIndex = await pathExists(portalIndex);
|
|
2649
|
+
if (options.forceBuild || !hasPortalIndex) {
|
|
2650
|
+
this.logPortalBuildHtml(
|
|
2651
|
+
item,
|
|
2652
|
+
"requested",
|
|
2653
|
+
options.forceBuild ? "forceBuild is enabled" : "dist/index.html does not exist"
|
|
2654
|
+
);
|
|
2655
|
+
await buildPortalStorageItem(portalDir, item, options);
|
|
2656
|
+
this.logPortalBuildHtml(item, "completed", "yarn build:html finished successfully");
|
|
2657
|
+
} else {
|
|
2658
|
+
this.logPortalBuildHtml(item, "skipped", "dist/index.html already exists");
|
|
1740
2659
|
}
|
|
1741
2660
|
return;
|
|
1742
2661
|
}
|
|
1743
|
-
|
|
2662
|
+
this.logPortalBuildHtml(item, "skipped", "the portal is disabled");
|
|
1744
2663
|
}
|
|
1745
|
-
async syncMultiPortalStorageItem(multiPortal, options, syncPrevious = false) {
|
|
2664
|
+
async syncMultiPortalStorageItem(multiPortal, options, syncPrevious = false, forceBuild = false) {
|
|
1746
2665
|
const currentItem = this.getMultiPortalStorageItem(multiPortal);
|
|
1747
2666
|
const previousItem = syncPrevious ? this.getMultiPortalStorageItem(multiPortal, true) : null;
|
|
1748
2667
|
await this.runPortalStorageTask(async () => {
|
|
1749
|
-
if (previousItem && (!currentItem || previousItem.appName !== currentItem.appName || previousItem.portalName !== currentItem.portalName
|
|
1750
|
-
await this.
|
|
2668
|
+
if (previousItem && (!currentItem || previousItem.appName !== currentItem.appName || previousItem.portalName !== currentItem.portalName)) {
|
|
2669
|
+
await this.removePortalStorageIndexHtml(previousItem);
|
|
1751
2670
|
}
|
|
1752
2671
|
if (currentItem) {
|
|
1753
|
-
await this.ensurePortalStorageItem(currentItem);
|
|
2672
|
+
await this.ensurePortalStorageItem(currentItem, { ...options, forceBuild });
|
|
1754
2673
|
}
|
|
1755
2674
|
}, options);
|
|
1756
2675
|
}
|
|
@@ -1793,12 +2712,12 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1793
2712
|
await next();
|
|
1794
2713
|
}
|
|
1795
2714
|
async deployPortalDist(ctx, next) {
|
|
1796
|
-
var _a, _b, _c
|
|
2715
|
+
var _a, _b, _c;
|
|
1797
2716
|
const deployCtx = ctx;
|
|
1798
|
-
const appName =
|
|
1799
|
-
const portalName = normalizePortalStorageName((
|
|
1800
|
-
const basePath = trimString((
|
|
1801
|
-
const filePath = trimString((
|
|
2717
|
+
const appName = this.getCurrentStorageAppName();
|
|
2718
|
+
const portalName = normalizePortalStorageName((_a = deployCtx.request.body) == null ? void 0 : _a.portal);
|
|
2719
|
+
const basePath = trimString((_b = deployCtx.request.body) == null ? void 0 : _b.basePath);
|
|
2720
|
+
const filePath = trimString((_c = deployCtx.request.file) == null ? void 0 : _c.path);
|
|
1802
2721
|
try {
|
|
1803
2722
|
if (!isValidPortalDeploySegment(appName)) {
|
|
1804
2723
|
throw new Error("Invalid app");
|
|
@@ -1835,9 +2754,9 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1835
2754
|
}
|
|
1836
2755
|
}
|
|
1837
2756
|
async pullPortalSource(ctx, next) {
|
|
1838
|
-
var _a
|
|
1839
|
-
const appName =
|
|
1840
|
-
const portalName = normalizePortalStorageName((
|
|
2757
|
+
var _a;
|
|
2758
|
+
const appName = this.getCurrentStorageAppName();
|
|
2759
|
+
const portalName = normalizePortalStorageName((_a = ctx.action.params.values) == null ? void 0 : _a.portal);
|
|
1841
2760
|
try {
|
|
1842
2761
|
if (!isValidPortalDeploySegment(appName)) {
|
|
1843
2762
|
throw new Error("Invalid app");
|
|
@@ -1857,11 +2776,11 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1857
2776
|
}
|
|
1858
2777
|
}
|
|
1859
2778
|
async pushPortalSource(ctx, next) {
|
|
1860
|
-
var _a, _b
|
|
2779
|
+
var _a, _b;
|
|
1861
2780
|
const sourceCtx = ctx;
|
|
1862
|
-
const appName =
|
|
1863
|
-
const portalName = normalizePortalStorageName((
|
|
1864
|
-
const filePath = trimString((
|
|
2781
|
+
const appName = this.getCurrentStorageAppName();
|
|
2782
|
+
const portalName = normalizePortalStorageName((_a = sourceCtx.request.body) == null ? void 0 : _a.portal);
|
|
2783
|
+
const filePath = trimString((_b = sourceCtx.request.file) == null ? void 0 : _b.path);
|
|
1865
2784
|
try {
|
|
1866
2785
|
if (!isValidPortalDeploySegment(appName)) {
|
|
1867
2786
|
throw new Error("Invalid app");
|
|
@@ -1913,62 +2832,43 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
1913
2832
|
}
|
|
1914
2833
|
async listAppPortalManifest(options) {
|
|
1915
2834
|
const records = await this.db.getRepository("multiPortals").find({
|
|
1916
|
-
filter:
|
|
2835
|
+
filter: {
|
|
1917
2836
|
enabled: true,
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
2837
|
+
uiLayoutUid: {
|
|
2838
|
+
$in: [...import_constants.MULTI_PORTAL_UI_LAYOUT_UIDS]
|
|
2839
|
+
}
|
|
2840
|
+
},
|
|
2841
|
+
fields: [...MULTI_PORTAL_ACCESSIBLE_FIELDS],
|
|
1922
2842
|
sort: ["uid"],
|
|
1923
2843
|
transaction: options == null ? void 0 : options.transaction
|
|
1924
2844
|
});
|
|
1925
|
-
return records.
|
|
1926
|
-
const
|
|
1927
|
-
|
|
1928
|
-
const uid = typeof portal.uid === "string" ? portal.uid : "";
|
|
1929
|
-
const title = typeof portal.title === "string" ? portal.title : "";
|
|
1930
|
-
return {
|
|
1931
|
-
uid,
|
|
1932
|
-
title,
|
|
1933
|
-
icon: typeof portal.icon === "string" ? portal.icon : null,
|
|
1934
|
-
portalType: typeof portal.portalType === "string" ? portal.portalType : null,
|
|
1935
|
-
routePath: String(portal.routePath || ""),
|
|
1936
|
-
layout: typeof (uiLayout == null ? void 0 : uiLayout.layoutType) === "string" ? uiLayout.layoutType : null
|
|
1937
|
-
};
|
|
1938
|
-
});
|
|
1939
|
-
}
|
|
1940
|
-
async getUiLayout(uid, options) {
|
|
1941
|
-
if (typeof uid !== "string" || !uid) {
|
|
1942
|
-
return null;
|
|
1943
|
-
}
|
|
1944
|
-
return this.db.getRepository("uiLayouts").findOne({
|
|
1945
|
-
filterByTk: uid,
|
|
1946
|
-
transaction: options == null ? void 0 : options.transaction
|
|
2845
|
+
return records.flatMap((record) => {
|
|
2846
|
+
const item = this.toAppPortalManifestItem(record);
|
|
2847
|
+
return item ? [item] : [];
|
|
1947
2848
|
});
|
|
1948
2849
|
}
|
|
1949
|
-
|
|
2850
|
+
toAppPortalManifestItem(multiPortal) {
|
|
1950
2851
|
const uid = getRecordField(multiPortal, "uid");
|
|
1951
2852
|
const title = getRecordField(multiPortal, "title");
|
|
1952
2853
|
const portalType = getRecordField(multiPortal, "portalType");
|
|
1953
2854
|
const routePath = getRecordField(multiPortal, "routePath");
|
|
1954
2855
|
const enabled = getRecordField(multiPortal, "enabled");
|
|
1955
|
-
if (typeof uid !== "string" || !uid ||
|
|
2856
|
+
if (typeof uid !== "string" || !uid || typeof title !== "string" || typeof routePath !== "string" || !routePath || enabled !== true) {
|
|
1956
2857
|
return null;
|
|
1957
2858
|
}
|
|
1958
2859
|
const uiLayoutUid = getRecordField(multiPortal, "uiLayoutUid");
|
|
1959
|
-
const
|
|
1960
|
-
if (!
|
|
2860
|
+
const layout = (0, import_constants.getMultiPortalLayoutType)(uiLayoutUid);
|
|
2861
|
+
if (!layout) {
|
|
1961
2862
|
return null;
|
|
1962
2863
|
}
|
|
1963
2864
|
const icon = getRecordField(multiPortal, "icon");
|
|
1964
|
-
const layoutType = getRecordField(uiLayout, "layoutType");
|
|
1965
2865
|
return {
|
|
1966
2866
|
uid,
|
|
1967
2867
|
title,
|
|
1968
2868
|
icon: typeof icon === "string" ? icon : null,
|
|
1969
2869
|
portalType: typeof portalType === "string" ? portalType : null,
|
|
1970
2870
|
routePath,
|
|
1971
|
-
layout
|
|
2871
|
+
layout
|
|
1972
2872
|
};
|
|
1973
2873
|
}
|
|
1974
2874
|
async setAppManifestItem(item, options) {
|
|
@@ -2011,30 +2911,13 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2011
2911
|
if (typeof uid !== "string" || !uid) {
|
|
2012
2912
|
return;
|
|
2013
2913
|
}
|
|
2014
|
-
const item =
|
|
2914
|
+
const item = this.toAppPortalManifestItem(multiPortal);
|
|
2015
2915
|
if (item) {
|
|
2016
2916
|
await this.setAppManifestItem(item, options);
|
|
2017
2917
|
return;
|
|
2018
2918
|
}
|
|
2019
2919
|
await this.removeAppManifestItem(uid, options);
|
|
2020
2920
|
}
|
|
2021
|
-
async publishUiLayoutManifestItems(uiLayout, options) {
|
|
2022
|
-
const uiLayoutUid = getRecordField(uiLayout, "uid");
|
|
2023
|
-
if (typeof uiLayoutUid !== "string" || !uiLayoutUid) {
|
|
2024
|
-
return;
|
|
2025
|
-
}
|
|
2026
|
-
const records = await this.db.getRepository("multiPortals").find({
|
|
2027
|
-
filter: withCustomMultiPortalFilter({
|
|
2028
|
-
uiLayoutUid
|
|
2029
|
-
}),
|
|
2030
|
-
fields: [...MULTI_PORTAL_ACCESSIBLE_QUERY_FIELDS],
|
|
2031
|
-
transaction: options == null ? void 0 : options.transaction
|
|
2032
|
-
});
|
|
2033
|
-
for (const record of records) {
|
|
2034
|
-
record.set("uiLayout", uiLayout);
|
|
2035
|
-
await this.publishAppManifestItem(record, options);
|
|
2036
|
-
}
|
|
2037
|
-
}
|
|
2038
2921
|
async reconcilePortalStorage(options) {
|
|
2039
2922
|
const records = await this.db.getRepository("multiPortals").find({
|
|
2040
2923
|
filter: {
|
|
@@ -2085,6 +2968,10 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2085
2968
|
}
|
|
2086
2969
|
}
|
|
2087
2970
|
async beforeLoad() {
|
|
2971
|
+
this.app.db.registerRepositories({ MultiPortalDesktopRouteRepository });
|
|
2972
|
+
this.app.resourceManager.registerPreActionHandler("roles:check", checkMultiPortalAccessForRolesCheck);
|
|
2973
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:list", addDesktopRouteEffectiveScopeFilter);
|
|
2974
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:get", addDesktopRouteEffectiveScopeFilter);
|
|
2088
2975
|
this.app.resourceManager.registerPreActionHandler(
|
|
2089
2976
|
"desktopRoutes:listAccessible",
|
|
2090
2977
|
addMultiPortalListAccessibleGuard
|
|
@@ -2094,25 +2981,30 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2094
2981
|
"desktopRoutes:listRolePermissionTargets",
|
|
2095
2982
|
mapMultiPortalLayoutToUiLayoutForRolePermissionTargets
|
|
2096
2983
|
);
|
|
2984
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:create", preventDirectDefaultPortalMutation);
|
|
2985
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:create", validateMultiPortalUiLayoutUidWrite);
|
|
2097
2986
|
this.app.resourceManager.registerPreActionHandler("multiPortals:create", captureSkipCreatePortalDirectory);
|
|
2098
2987
|
this.app.resourceManager.registerPreActionHandler("multiPortals:create", normalizeMultiPortalSlugValues);
|
|
2099
|
-
this.app.resourceManager.registerPreActionHandler("multiPortals:
|
|
2100
|
-
this.app.resourceManager.registerPreActionHandler("multiPortals:update",
|
|
2988
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:update", preventDirectDefaultPortalMutation);
|
|
2989
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:update", validateMultiPortalUiLayoutUidWrite);
|
|
2101
2990
|
this.app.resourceManager.registerPreActionHandler("multiPortals:update", normalizeMultiPortalSlugValues);
|
|
2102
|
-
this.app.resourceManager.registerPreActionHandler("multiPortals:
|
|
2991
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", preventDirectDefaultPortalMutation);
|
|
2992
|
+
this.app.resourceManager.registerPreActionHandler(
|
|
2993
|
+
"multiPortals:firstOrCreate",
|
|
2994
|
+
validateMultiPortalUiLayoutUidWrite
|
|
2995
|
+
);
|
|
2103
2996
|
this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", captureSkipCreatePortalDirectory);
|
|
2104
2997
|
this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", normalizeMultiPortalSlugValues);
|
|
2105
|
-
this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", preventUiLayoutRouteNameConflict);
|
|
2106
|
-
this.app.resourceManager.registerPreActionHandler("desktopRoutes:create", captureExplicitDesktopRouteUiLayouts, {
|
|
2107
|
-
before: UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG
|
|
2108
|
-
});
|
|
2109
2998
|
this.app.resourceManager.registerPreActionHandler(
|
|
2110
|
-
"
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2999
|
+
"multiPortals:updateOrCreate",
|
|
3000
|
+
preventDirectDefaultPortalMutation
|
|
3001
|
+
);
|
|
3002
|
+
this.app.resourceManager.registerPreActionHandler(
|
|
3003
|
+
"multiPortals:updateOrCreate",
|
|
3004
|
+
validateMultiPortalUiLayoutUidWrite
|
|
2115
3005
|
);
|
|
3006
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:updateOrCreate", captureSkipCreatePortalDirectory);
|
|
3007
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:updateOrCreate", normalizeMultiPortalSlugValues);
|
|
2116
3008
|
this.app.resourceManager.registerPreActionHandler("desktopRoutes:create", addDesktopRouteCreateMultiPortal, {
|
|
2117
3009
|
after: UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG
|
|
2118
3010
|
});
|
|
@@ -2123,6 +3015,9 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2123
3015
|
after: UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG
|
|
2124
3016
|
}
|
|
2125
3017
|
);
|
|
3018
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:update", guardDesktopRouteUpdateScope);
|
|
3019
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:move", guardDesktopRouteMoveScope);
|
|
3020
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:destroy", destroyScopedDesktopRoutes);
|
|
2126
3021
|
}
|
|
2127
3022
|
async load() {
|
|
2128
3023
|
this.app.acl.registerSnippet({
|
|
@@ -2134,12 +3029,15 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2134
3029
|
actions: ROLE_MULTI_PORTAL_PERMISSION_ACTIONS
|
|
2135
3030
|
});
|
|
2136
3031
|
this.app.acl.allow("multiPortals", "listEnabled", "public");
|
|
3032
|
+
this.app.acl.allow("multiPortals", "getDefault", "public");
|
|
2137
3033
|
this.app.acl.allow("multiPortals", "listAccessible", "loggedIn");
|
|
2138
3034
|
this.app.resourceManager.use(createPortalDeployUploadMiddleware(), {
|
|
2139
3035
|
tag: "multiPortalDeployUpload",
|
|
2140
3036
|
after: "acl"
|
|
2141
3037
|
});
|
|
2142
3038
|
this.app.resourceManager.registerActionHandler("multiPortals:listEnabled", listEnabledMultiPortals);
|
|
3039
|
+
this.app.resourceManager.registerActionHandler("multiPortals:getDefault", getDefaultMultiPortal);
|
|
3040
|
+
this.app.resourceManager.registerActionHandler("multiPortals:setDefault", setDefaultMultiPortal);
|
|
2143
3041
|
this.app.resourceManager.registerActionHandler("multiPortals:listAccessible", listAccessibleMultiPortals);
|
|
2144
3042
|
this.app.resourceManager.registerActionHandler("multiPortals:deploy", async (ctx, next) => {
|
|
2145
3043
|
await this.deployPortalDist(ctx, next);
|
|
@@ -2163,13 +3061,19 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2163
3061
|
this.app.db.on("roles.beforeCreate", (role) => {
|
|
2164
3062
|
(0, import_ensureDefaultRoleMultiPortalAccess.applyDefaultRoleMultiPortalAccess)(role);
|
|
2165
3063
|
});
|
|
3064
|
+
this.app.db.on("desktopRoutes.afterCreate", reconcileCreatedDesktopRoutePermissions);
|
|
3065
|
+
this.app.db.on("multiPortals.beforeUpdate", (multiPortal) => {
|
|
3066
|
+
if (multiPortal.get("enabled") === false && multiPortal.get("isDefault") === true) {
|
|
3067
|
+
multiPortal.set("isDefault", null);
|
|
3068
|
+
}
|
|
3069
|
+
});
|
|
2166
3070
|
this.app.db.on("multiPortals.afterCreate", async (multiPortal, options) => {
|
|
2167
3071
|
await grantDefaultAccessToNewMultiPortal(this.app.db, multiPortal, options);
|
|
2168
3072
|
await this.publishAppManifestItem(multiPortal, options);
|
|
2169
3073
|
if (shouldSkipCreatePortalDirectory(options)) {
|
|
2170
3074
|
return;
|
|
2171
3075
|
}
|
|
2172
|
-
await this.syncMultiPortalStorageItem(multiPortal, options);
|
|
3076
|
+
await this.syncMultiPortalStorageItem(multiPortal, options, false, true);
|
|
2173
3077
|
});
|
|
2174
3078
|
this.app.db.on("multiPortals.afterUpdate", async (multiPortal, options) => {
|
|
2175
3079
|
await this.publishAppManifestItem(multiPortal, options);
|
|
@@ -2182,19 +3086,28 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2182
3086
|
}
|
|
2183
3087
|
await this.removeMultiPortalStorageItem(multiPortal, options);
|
|
2184
3088
|
});
|
|
2185
|
-
this.app.db.on("uiLayouts.afterUpdate", async (uiLayout, options) => {
|
|
2186
|
-
await this.publishUiLayoutManifestItems(uiLayout, options);
|
|
2187
|
-
});
|
|
2188
3089
|
}
|
|
2189
3090
|
async install() {
|
|
2190
|
-
|
|
2191
|
-
|
|
3091
|
+
var _a, _b;
|
|
3092
|
+
if (!this.deprecatedInitPortalEnvWarningEmitted && hasDeprecatedInitPortalEnv()) {
|
|
3093
|
+
this.deprecatedInitPortalEnvWarningEmitted = true;
|
|
3094
|
+
(_b = (_a = this.app.logger) == null ? void 0 : _a.warn) == null ? void 0 : _b.call(
|
|
3095
|
+
_a,
|
|
3096
|
+
'INIT_PORTAL_TYPE and INIT_PORTAL_NAME are deprecated and no longer affect multi-portal seeding; NocoBase now creates the AI Portal "main" plus the fixed no-code "admin" and "mobile" portals by default.'
|
|
3097
|
+
);
|
|
3098
|
+
}
|
|
3099
|
+
const version = await this.app.version.get();
|
|
3100
|
+
if (!version) {
|
|
3101
|
+
await (0, import_ensureDefaultRoleMultiPortalAccess.ensureDefaultRoleMultiPortalAccess)(this.db);
|
|
3102
|
+
await seedFreshMultiPortals(this.db);
|
|
3103
|
+
} else {
|
|
3104
|
+
await seedHistoricalMultiPortals(this.db);
|
|
3105
|
+
}
|
|
2192
3106
|
await this.reconcilePortalStorage();
|
|
2193
3107
|
await this.reconcileAppManifest();
|
|
2194
3108
|
}
|
|
2195
3109
|
async afterEnable() {
|
|
2196
|
-
await
|
|
2197
|
-
await (0, import_ensureDefaultRoleMultiPortalAccess.ensureDefaultRoleMultiPortalAccess)(this.db);
|
|
3110
|
+
await repairFixedLayoutMultiPortalRecords(this.db);
|
|
2198
3111
|
await this.reconcilePortalStorage();
|
|
2199
3112
|
await this.reconcileAppManifest();
|
|
2200
3113
|
}
|