@nocobase/plugin-multi-portal 2.2.0-alpha.12 → 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 +7 -5
- 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 +4 -5
- package/dist/client-v2/index.js +1 -1
- package/dist/client-v2/interceptor.d.ts +13 -0
- package/dist/client-v2/layoutRegistration.d.ts +3 -6
- package/dist/client-v2/models/MultiPortalLayoutModels.d.ts +14 -0
- package/dist/client-v2/models/MultiPortalMobilePageModels.d.ts +2 -0
- package/dist/client-v2/pages/MultiPortalsPage.d.ts +10 -27
- package/dist/client-v2/portalAccess.d.ts +73 -0
- package/dist/client-v2/routeUrl.d.ts +1 -0
- package/dist/constants.d.ts +8 -0
- package/dist/constants.js +30 -2
- package/dist/externalVersion.js +9 -8
- package/dist/locale/en-US.json +45 -14
- package/dist/locale/zh-CN.json +43 -12
- package/dist/node_modules/tar/package.json +1 -1
- package/dist/server/collections/multiPortals.js +12 -11
- package/dist/server/plugin.d.ts +6 -4
- package/dist/server/plugin.js +487 -312
- package/dist/swagger/index.d.ts +152 -0
- package/dist/swagger/index.js +142 -0
- package/package.json +3 -3
- package/dist/client-v2/354.7be267ae5d61a878.js +0 -10
- package/dist/client-v2/499.ef2ff0c022687485.js +0 -10
- package/dist/client-v2/556.5faf86d4f8faef40.js +0 -10
- package/dist/client-v2/778.c98e7ec542d931fe.js +0 -10
package/dist/server/plugin.js
CHANGED
|
@@ -60,9 +60,9 @@ const MULTI_PORTAL_RUNTIME_FIELDS = [
|
|
|
60
60
|
"portalName",
|
|
61
61
|
"routePath",
|
|
62
62
|
"authCheck",
|
|
63
|
-
"enabled"
|
|
63
|
+
"enabled",
|
|
64
|
+
"uiLayoutUid"
|
|
64
65
|
];
|
|
65
|
-
const MULTI_PORTAL_RUNTIME_QUERY_FIELDS = [...MULTI_PORTAL_RUNTIME_FIELDS, "uiLayoutUid"];
|
|
66
66
|
const MULTI_PORTAL_ACCESSIBLE_FIELDS = [
|
|
67
67
|
"uid",
|
|
68
68
|
"title",
|
|
@@ -71,18 +71,15 @@ const MULTI_PORTAL_ACCESSIBLE_FIELDS = [
|
|
|
71
71
|
"portalName",
|
|
72
72
|
"routePath",
|
|
73
73
|
"authCheck",
|
|
74
|
-
"enabled"
|
|
74
|
+
"enabled",
|
|
75
|
+
"uiLayoutUid"
|
|
75
76
|
];
|
|
76
|
-
const MULTI_PORTAL_ACCESSIBLE_QUERY_FIELDS = [...MULTI_PORTAL_ACCESSIBLE_FIELDS, "uiLayoutUid"];
|
|
77
|
-
const MULTI_PORTAL_UI_LAYOUT_RUNTIME_FIELDS = ["layoutType"];
|
|
78
77
|
const DESKTOP_ROUTE_ROLE_PERMISSION_TARGET_FIELDS = ["id", "title", "hidden", "parentId", "options"];
|
|
79
78
|
const UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG = "plugin-ui-layout:desktop-route-write-layout";
|
|
80
79
|
const MAIN_APP_NAME = "main";
|
|
81
80
|
const UNION_ROLE_KEY = "__union__";
|
|
82
81
|
const MULTI_PORTAL_MANIFEST_NAMESPACE = "multi-portal";
|
|
83
82
|
const MULTI_PORTAL_MANIFEST_SYNC_MESSAGE_TYPE = "multi-portal:app-manifest-changed";
|
|
84
|
-
const DEFAULT_INIT_PORTAL_TYPE = "ai";
|
|
85
|
-
const DEFAULT_INIT_PORTAL_NAME = "main";
|
|
86
83
|
const DEFAULT_INIT_PORTAL_TEMPLATE = "@nocobase/portal-template-default";
|
|
87
84
|
const PORTAL_CLIENT_PREFIX = "x";
|
|
88
85
|
const PORTAL_DEPLOY_UPLOAD_LIMIT = 200 * 1024 * 1024;
|
|
@@ -92,7 +89,9 @@ const PORTAL_PUBLIC_FILE_MODE = 420;
|
|
|
92
89
|
const PORTAL_TEMPLATE_NPM_PACK_TIMEOUT_MS = 3e4;
|
|
93
90
|
const DEFAULT_MULTI_PORTAL_UID = "__default_portal__";
|
|
94
91
|
const MULTI_PORTAL_SLUG_PATTERN = /^[a-z0-9_-]+$/;
|
|
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,6 +99,7 @@ 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",
|
|
@@ -119,6 +119,11 @@ const ROLE_MULTI_PORTAL_PERMISSION_ACTIONS = [
|
|
|
119
119
|
"rolesMultiPortalDesktopRoutes:*",
|
|
120
120
|
"rolesMultiPortalRoutePolicies:*"
|
|
121
121
|
];
|
|
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
|
+
}
|
|
122
127
|
const SKIP_CREATE_PORTAL_DIRECTORY = Symbol("skipCreatePortalDirectory");
|
|
123
128
|
function getRecordField(record, field) {
|
|
124
129
|
if (!record || typeof record !== "object") {
|
|
@@ -144,46 +149,28 @@ async function pathExists(filePath) {
|
|
|
144
149
|
function trimString(value) {
|
|
145
150
|
return String(value ?? "").trim();
|
|
146
151
|
}
|
|
147
|
-
function isInitPortalType(value) {
|
|
148
|
-
return INIT_PORTAL_TYPES.includes(value);
|
|
149
|
-
}
|
|
150
|
-
function getInitPortalType() {
|
|
151
|
-
const portalType = trimString(process.env.INIT_PORTAL_TYPE) || DEFAULT_INIT_PORTAL_TYPE;
|
|
152
|
-
if (!isInitPortalType(portalType)) {
|
|
153
|
-
throw new Error('INIT_PORTAL_TYPE must be either "no-code" or "ai".');
|
|
154
|
-
}
|
|
155
|
-
return portalType;
|
|
156
|
-
}
|
|
157
|
-
function getInitPortalName() {
|
|
158
|
-
const portalName = trimString(process.env.INIT_PORTAL_NAME) || DEFAULT_INIT_PORTAL_NAME;
|
|
159
|
-
if (!MULTI_PORTAL_SLUG_PATTERN.test(portalName)) {
|
|
160
|
-
throw new Error("INIT_PORTAL_NAME can only contain lowercase letters, numbers, hyphens, and underscores.");
|
|
161
|
-
}
|
|
162
|
-
return portalName;
|
|
163
|
-
}
|
|
164
152
|
function getInitPortalTemplate() {
|
|
165
153
|
return trimString(process.env.INIT_PORTAL_TEMPLATE) || DEFAULT_INIT_PORTAL_TEMPLATE;
|
|
166
154
|
}
|
|
167
|
-
function
|
|
168
|
-
|
|
169
|
-
return title || portalName;
|
|
155
|
+
function hasDeprecatedInitPortalEnv() {
|
|
156
|
+
return Boolean(trimString(process.env.INIT_PORTAL_TYPE) || trimString(process.env.INIT_PORTAL_NAME));
|
|
170
157
|
}
|
|
171
|
-
function
|
|
172
|
-
const portalName = getInitPortalName();
|
|
158
|
+
function getDefaultAiMultiPortalRecord(options = {}) {
|
|
173
159
|
return {
|
|
174
160
|
uid: DEFAULT_MULTI_PORTAL_UID,
|
|
175
|
-
title:
|
|
161
|
+
title: "Main",
|
|
176
162
|
icon: "DesktopOutlined",
|
|
177
|
-
portalType:
|
|
178
|
-
portalName,
|
|
179
|
-
routePath:
|
|
163
|
+
portalType: "ai",
|
|
164
|
+
portalName: "main",
|
|
165
|
+
routePath: "/main",
|
|
180
166
|
authCheck: true,
|
|
181
167
|
enabled: true,
|
|
182
|
-
|
|
168
|
+
...options.isDefault ? { isDefault: true } : {},
|
|
169
|
+
uiLayoutUid: import_constants.ADMIN_UI_LAYOUT_UID
|
|
183
170
|
};
|
|
184
171
|
}
|
|
185
172
|
function getFreshMultiPortalRecords() {
|
|
186
|
-
return [
|
|
173
|
+
return [getDefaultAiMultiPortalRecord({ isDefault: true }), ...getFixedLayoutMultiPortalRecords()];
|
|
187
174
|
}
|
|
188
175
|
function getFixedLayoutMultiPortalRecords() {
|
|
189
176
|
return [
|
|
@@ -196,7 +183,7 @@ function getFixedLayoutMultiPortalRecords() {
|
|
|
196
183
|
routePath: "/admin",
|
|
197
184
|
authCheck: true,
|
|
198
185
|
enabled: true,
|
|
199
|
-
uiLayoutUid:
|
|
186
|
+
uiLayoutUid: import_constants.ADMIN_UI_LAYOUT_UID
|
|
200
187
|
},
|
|
201
188
|
{
|
|
202
189
|
uid: import_constants.DEFAULT_MOBILE_MULTI_PORTAL_UID,
|
|
@@ -207,7 +194,7 @@ function getFixedLayoutMultiPortalRecords() {
|
|
|
207
194
|
routePath: "/mobile",
|
|
208
195
|
authCheck: true,
|
|
209
196
|
enabled: true,
|
|
210
|
-
uiLayoutUid:
|
|
197
|
+
uiLayoutUid: import_constants.MOBILE_UI_LAYOUT_UID
|
|
211
198
|
}
|
|
212
199
|
];
|
|
213
200
|
}
|
|
@@ -292,6 +279,13 @@ function getPortalDeployBasePath(appName, portalName) {
|
|
|
292
279
|
const portalPath = appName === MAIN_APP_NAME ? `/${PORTAL_CLIENT_PREFIX}/${portalName}/` : `/${PORTAL_CLIENT_PREFIX}/apps/${appName}/${portalName}/`;
|
|
293
280
|
return resolvePortalStoragePublicPath(joinPortalStoragePublicPath(process.env.APP_PUBLIC_PATH || "/", portalPath));
|
|
294
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
|
+
}
|
|
295
289
|
function createPortalDeployUploadMiddleware() {
|
|
296
290
|
const storage = import_utils.koaMulter.diskStorage({
|
|
297
291
|
destination: import_os.default.tmpdir(),
|
|
@@ -434,80 +428,13 @@ async function resolvePortalTemplate(templateSource, logPath) {
|
|
|
434
428
|
return downloadPortalTemplatePackage(templateSource, logPath);
|
|
435
429
|
}
|
|
436
430
|
async function copyPortalTemplate(sourceDir, targetDir) {
|
|
437
|
-
const ignoredSegments = /* @__PURE__ */ new Set([".git", "node_modules", ".DS_Store"]);
|
|
431
|
+
const ignoredSegments = /* @__PURE__ */ new Set([".git", "node_modules", ".DS_Store", ".env", ".env.local"]);
|
|
438
432
|
await import_fs.default.promises.mkdir(import_path.default.dirname(targetDir), { recursive: true });
|
|
439
433
|
await import_fs.default.promises.cp(sourceDir, targetDir, {
|
|
440
434
|
recursive: true,
|
|
441
435
|
filter: (source) => !import_path.default.relative(sourceDir, source).split(import_path.default.sep).some((segment) => segment.startsWith("._") || ignoredSegments.has(segment))
|
|
442
436
|
});
|
|
443
437
|
}
|
|
444
|
-
function getPortalStorageConfig(options) {
|
|
445
|
-
const sourceOptions = isRecordLike(options) ? options : {};
|
|
446
|
-
const sourceStorage = trimString(sourceOptions.sourceStorage);
|
|
447
|
-
if (sourceStorage !== "git") {
|
|
448
|
-
return { sourceStorage: "nocobase" };
|
|
449
|
-
}
|
|
450
|
-
const gitOptions = isRecordLike(sourceOptions.git) ? sourceOptions.git : {};
|
|
451
|
-
return {
|
|
452
|
-
sourceStorage,
|
|
453
|
-
git: {
|
|
454
|
-
repo: trimString(gitOptions.repo),
|
|
455
|
-
branch: trimString(gitOptions.branch) || "main",
|
|
456
|
-
path: trimString(gitOptions.path) || "."
|
|
457
|
-
}
|
|
458
|
-
};
|
|
459
|
-
}
|
|
460
|
-
function upsertPortalEnvContent(content, values) {
|
|
461
|
-
const nextValues = { ...values };
|
|
462
|
-
const lines = content ? content.replace(/\r\n/g, "\n").split("\n") : [];
|
|
463
|
-
const result = [];
|
|
464
|
-
for (const line of lines) {
|
|
465
|
-
if (!line && result.length === lines.length - 1) {
|
|
466
|
-
continue;
|
|
467
|
-
}
|
|
468
|
-
const match = line.match(/^(\s*)([A-Za-z_][A-Za-z0-9_]*)\s*=/);
|
|
469
|
-
const key = match == null ? void 0 : match[2];
|
|
470
|
-
if (key && Object.prototype.hasOwnProperty.call(nextValues, key)) {
|
|
471
|
-
result.push(`${key}=${nextValues[key]}`);
|
|
472
|
-
delete nextValues[key];
|
|
473
|
-
continue;
|
|
474
|
-
}
|
|
475
|
-
result.push(line);
|
|
476
|
-
}
|
|
477
|
-
for (const [key, value] of Object.entries(nextValues)) {
|
|
478
|
-
result.push(`${key}=${value}`);
|
|
479
|
-
}
|
|
480
|
-
return `${result.join("\n").replace(/\n*$/, "")}
|
|
481
|
-
`;
|
|
482
|
-
}
|
|
483
|
-
async function upsertPortalEnvFile(filePath, values) {
|
|
484
|
-
let content = "";
|
|
485
|
-
try {
|
|
486
|
-
content = await import_fs.default.promises.readFile(filePath, "utf-8");
|
|
487
|
-
} catch {
|
|
488
|
-
content = "";
|
|
489
|
-
}
|
|
490
|
-
await import_fs.default.promises.writeFile(filePath, upsertPortalEnvContent(content, values), "utf-8");
|
|
491
|
-
}
|
|
492
|
-
async function ensurePortalStorageConfigFiles(portalDir, item) {
|
|
493
|
-
const apiUrl = getPortalStorageApiUrl(item.appName);
|
|
494
|
-
const portalBase = getPortalDeployBasePath(item.appName, item.portalName);
|
|
495
|
-
await import_fs.default.promises.mkdir(portalDir, { recursive: true });
|
|
496
|
-
await upsertPortalEnvFile(import_path.default.join(portalDir, ".env"), {
|
|
497
|
-
NOCOBASE_API_URL: resolvePortalStorageEnvApiUrl(apiUrl),
|
|
498
|
-
NOCOBASE_PORTAL_BASE: portalBase
|
|
499
|
-
});
|
|
500
|
-
await upsertPortalEnvFile(import_path.default.join(portalDir, ".env.local"), {
|
|
501
|
-
NOCOBASE_API_URL: apiUrl,
|
|
502
|
-
NOCOBASE_PORTAL_BASE: portalBase
|
|
503
|
-
});
|
|
504
|
-
await import_fs.default.promises.writeFile(
|
|
505
|
-
import_path.default.join(portalDir, "portal.config.json"),
|
|
506
|
-
`${JSON.stringify(item.config, null, 2)}
|
|
507
|
-
`,
|
|
508
|
-
"utf-8"
|
|
509
|
-
);
|
|
510
|
-
}
|
|
511
438
|
function sanitizePortalStorageNodeOptions(value) {
|
|
512
439
|
return trimString(value).split(/\s+/).filter((option) => option !== "--preserve-symlinks" && option !== "--preserve-symlinks-main").join(" ");
|
|
513
440
|
}
|
|
@@ -592,17 +519,24 @@ async function runPortalStorageCommandOnce(command, args, options) {
|
|
|
592
519
|
throw error;
|
|
593
520
|
}
|
|
594
521
|
}
|
|
595
|
-
|
|
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) {
|
|
596
527
|
const logPath = getPortalStorageLogPath(item);
|
|
528
|
+
const buildAppName = hasRequestAppHeader(options) ? item.appName : MAIN_APP_NAME;
|
|
597
529
|
const buildEnv = getPortalStorageCommandEnv({
|
|
598
|
-
NOCOBASE_API_URL: getPortalStorageApiUrl(
|
|
599
|
-
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",
|
|
600
534
|
COREPACK_ENABLE_PROJECT_SPEC: "0"
|
|
601
535
|
});
|
|
602
536
|
await appendPortalStorageLog(logPath, `Building portal ${item.appName}/${item.portalName}.`);
|
|
603
537
|
await appendPortalStorageLog(
|
|
604
538
|
logPath,
|
|
605
|
-
`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 || ""}`
|
|
606
540
|
);
|
|
607
541
|
await runPortalStorageCommandOnce("yarn", ["build:html"], {
|
|
608
542
|
cwd: portalDir,
|
|
@@ -616,9 +550,9 @@ function validatePortalDeployBasePath(appName, portalName, basePath) {
|
|
|
616
550
|
if (normalizedBasePath.includes("..")) {
|
|
617
551
|
throw new Error('basePath cannot contain ".."');
|
|
618
552
|
}
|
|
619
|
-
const
|
|
620
|
-
if (normalizedBasePath
|
|
621
|
-
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 ")}`);
|
|
622
556
|
}
|
|
623
557
|
}
|
|
624
558
|
function isPortalDeployTarEntry(entry) {
|
|
@@ -856,19 +790,11 @@ function collectDesktopRouteIds(record) {
|
|
|
856
790
|
}
|
|
857
791
|
return uniqueDesktopRouteIds(routeIds);
|
|
858
792
|
}
|
|
859
|
-
function pickMultiPortalUiLayoutRuntimeFields(record) {
|
|
860
|
-
const result = {};
|
|
861
|
-
for (const field of MULTI_PORTAL_UI_LAYOUT_RUNTIME_FIELDS) {
|
|
862
|
-
result[field] = getRecordField(record, field);
|
|
863
|
-
}
|
|
864
|
-
return result;
|
|
865
|
-
}
|
|
866
793
|
function pickMultiPortalRuntimeFields(record) {
|
|
867
794
|
const result = {};
|
|
868
795
|
for (const field of MULTI_PORTAL_RUNTIME_FIELDS) {
|
|
869
796
|
result[field] = getRecordField(record, field);
|
|
870
797
|
}
|
|
871
|
-
result.uiLayout = pickMultiPortalUiLayoutRuntimeFields(getRecordField(record, "uiLayout"));
|
|
872
798
|
return result;
|
|
873
799
|
}
|
|
874
800
|
function pickMultiPortalAccessibleFields(record) {
|
|
@@ -876,7 +802,6 @@ function pickMultiPortalAccessibleFields(record) {
|
|
|
876
802
|
for (const field of MULTI_PORTAL_ACCESSIBLE_FIELDS) {
|
|
877
803
|
result[field] = getRecordField(record, field) ?? null;
|
|
878
804
|
}
|
|
879
|
-
result.uiLayout = pickMultiPortalUiLayoutRuntimeFields(getRecordField(record, "uiLayout"));
|
|
880
805
|
return result;
|
|
881
806
|
}
|
|
882
807
|
function getExplicitRequestedLayoutUid(layout) {
|
|
@@ -1033,6 +958,37 @@ async function normalizeMultiPortalSlugValues(ctx, next) {
|
|
|
1033
958
|
values.portalName = slug;
|
|
1034
959
|
values.routePath = `/${slug}`;
|
|
1035
960
|
}
|
|
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
|
+
}
|
|
982
|
+
}
|
|
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
|
+
}
|
|
991
|
+
}
|
|
1036
992
|
await next();
|
|
1037
993
|
}
|
|
1038
994
|
function isUniqueConstraintViolation(error) {
|
|
@@ -1087,55 +1043,33 @@ async function seedFreshMultiPortals(db) {
|
|
|
1087
1043
|
}
|
|
1088
1044
|
}
|
|
1089
1045
|
async function seedHistoricalMultiPortals(db) {
|
|
1090
|
-
|
|
1091
|
-
await createDefaultMultiPortalBestEffort(db, getDefaultMultiPortalRecord());
|
|
1092
|
-
}
|
|
1046
|
+
await createDefaultMultiPortalBestEffort(db, getDefaultAiMultiPortalRecord());
|
|
1093
1047
|
for (const portal of getFixedLayoutMultiPortalRecords()) {
|
|
1094
1048
|
await createDefaultMultiPortalBestEffort(db, portal);
|
|
1095
1049
|
}
|
|
1096
1050
|
await repairFixedLayoutMultiPortalRecords(db);
|
|
1097
1051
|
}
|
|
1098
|
-
async function
|
|
1099
|
-
|
|
1052
|
+
async function validateMultiPortalUiLayoutUidWrite(ctx, next) {
|
|
1053
|
+
var _a;
|
|
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";
|
|
1100
1057
|
for (const { existing, values } of targets) {
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
const uiLayoutUid = trimString(values.uiLayoutUid ?? (existing == null ? void 0 : existing.get("uiLayoutUid")));
|
|
1105
|
-
const fixedPortal = getFixedLayoutMultiPortalRecord(existingUid) || getFixedLayoutMultiPortalRecord(uid);
|
|
1106
|
-
const canonicalPortal = (fixedPortal == null ? void 0 : fixedPortal.uid) === uid && (!existingUid || fixedPortal.uid === existingUid) && fixedPortal.portalName === portalName && fixedPortal.uiLayoutUid === uiLayoutUid;
|
|
1107
|
-
if (fixedPortal && !canonicalPortal) {
|
|
1108
|
-
ctx.throw(400, "Reserved Portal UID must match its canonical route and UI layout");
|
|
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(", ")}`);
|
|
1109
1061
|
return;
|
|
1110
1062
|
}
|
|
1111
|
-
if (!
|
|
1112
|
-
continue;
|
|
1113
|
-
}
|
|
1114
|
-
if (portalName === "admin" || portalName === "mobile") {
|
|
1063
|
+
if (!hasUiLayoutUid) {
|
|
1115
1064
|
continue;
|
|
1116
1065
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
filter: {
|
|
1120
|
-
routeName: portalName
|
|
1121
|
-
},
|
|
1122
|
-
fields: ["uid"]
|
|
1123
|
-
});
|
|
1124
|
-
if (uiLayout && !existingInitPortal) {
|
|
1125
|
-
ctx.throw(400, "Portal route name conflicts with an existing UI layout");
|
|
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(", ")}`);
|
|
1126
1068
|
return;
|
|
1127
1069
|
}
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
async function preventMultiPortalBackingLayoutChange(ctx, next) {
|
|
1132
|
-
const targets = await getMultiPortalWriteTargets(ctx, ["uiLayoutUid"]);
|
|
1133
|
-
for (const { existing, values } of targets) {
|
|
1134
|
-
if (!Object.prototype.hasOwnProperty.call(values, "uiLayoutUid")) {
|
|
1135
|
-
continue;
|
|
1136
|
-
}
|
|
1137
|
-
if (existing && existing.get("uiLayoutUid") !== values.uiLayoutUid) {
|
|
1138
|
-
ctx.throw(400, "Portal backing UI layout cannot be changed");
|
|
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");
|
|
1139
1073
|
return;
|
|
1140
1074
|
}
|
|
1141
1075
|
}
|
|
@@ -1163,20 +1097,8 @@ async function findRequestedMultiPortal(ctx, transaction) {
|
|
|
1163
1097
|
ctx.throw(400, `Portal '${portalUid}' does not support desktop routes`);
|
|
1164
1098
|
}
|
|
1165
1099
|
const uiLayoutUid = portal.get("uiLayoutUid");
|
|
1166
|
-
if (
|
|
1167
|
-
ctx.throw(400, `Portal '${portalUid}' has
|
|
1168
|
-
}
|
|
1169
|
-
const uiLayout = await ctx.db.getRepository("uiLayouts").findOne({
|
|
1170
|
-
filter: {
|
|
1171
|
-
uid: uiLayoutUid,
|
|
1172
|
-
enabled: true
|
|
1173
|
-
},
|
|
1174
|
-
fields: ["uid"],
|
|
1175
|
-
...transaction ? { lock: transaction.LOCK.UPDATE } : {},
|
|
1176
|
-
transaction
|
|
1177
|
-
});
|
|
1178
|
-
if (!uiLayout) {
|
|
1179
|
-
ctx.throw(400, `Portal '${portalUid}' has no enabled backing UI layout`);
|
|
1100
|
+
if (!(0, import_constants.isMultiPortalUiLayoutUid)(uiLayoutUid)) {
|
|
1101
|
+
ctx.throw(400, `Portal '${portalUid}' has an unsupported UI layout UID`);
|
|
1180
1102
|
}
|
|
1181
1103
|
const usesLayoutPermissions = (0, import_constants.isDefaultLayoutMultiPortalUid)(portalUid);
|
|
1182
1104
|
return {
|
|
@@ -1242,7 +1164,6 @@ async function assertDesktopRouteParentsBelongToScope(ctx, scope, values = ((_a)
|
|
|
1242
1164
|
id: parentIds,
|
|
1243
1165
|
...scope.filter
|
|
1244
1166
|
},
|
|
1245
|
-
...transaction ? { lock: transaction.LOCK.UPDATE } : {},
|
|
1246
1167
|
transaction
|
|
1247
1168
|
});
|
|
1248
1169
|
if (new Set(normalizeDesktopRouteTargetIds(parents)).size !== new Set(parentIds.map(String)).size) {
|
|
@@ -1276,7 +1197,6 @@ async function assertDesktopRouteUpsertMatchesBelongToScope(ctx, scope, values =
|
|
|
1276
1197
|
...filter,
|
|
1277
1198
|
...scope.filter
|
|
1278
1199
|
},
|
|
1279
|
-
...transaction ? { lock: transaction.LOCK.UPDATE } : {},
|
|
1280
1200
|
transaction
|
|
1281
1201
|
});
|
|
1282
1202
|
const allMatchIds = normalizeDesktopRouteTargetIds(allMatches);
|
|
@@ -1294,13 +1214,77 @@ async function canAccessMultiPortal(ctx, multiPortalUid) {
|
|
|
1294
1214
|
if (!currentRoles.length) {
|
|
1295
1215
|
return false;
|
|
1296
1216
|
}
|
|
1297
|
-
const
|
|
1217
|
+
const access = await ctx.db.getRepository("rolesMultiPortals").findOne({
|
|
1218
|
+
fields: ["id"],
|
|
1298
1219
|
filter: {
|
|
1299
1220
|
roleName: currentRoles,
|
|
1300
1221
|
multiPortalUid
|
|
1301
1222
|
}
|
|
1302
1223
|
});
|
|
1303
|
-
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
|
+
};
|
|
1304
1288
|
}
|
|
1305
1289
|
async function listCurrentRoleAccessibleMultiPortalUids(ctx) {
|
|
1306
1290
|
const currentRoles = getCurrentRoles(ctx);
|
|
@@ -1363,7 +1347,7 @@ async function removeRouteIdsWithUnauthorizedAncestors(ctx, routeIds) {
|
|
|
1363
1347
|
}
|
|
1364
1348
|
}
|
|
1365
1349
|
}
|
|
1366
|
-
async function getMultiPortalAccessibleRouteIds(ctx,
|
|
1350
|
+
async function getMultiPortalAccessibleRouteIds(ctx, portalContext) {
|
|
1367
1351
|
const currentRoles = getCurrentRoles(ctx);
|
|
1368
1352
|
if (currentRoles.includes("root")) {
|
|
1369
1353
|
return;
|
|
@@ -1371,11 +1355,11 @@ async function getMultiPortalAccessibleRouteIds(ctx, multiPortalUid) {
|
|
|
1371
1355
|
if (!currentRoles.length) {
|
|
1372
1356
|
return /* @__PURE__ */ new Set();
|
|
1373
1357
|
}
|
|
1374
|
-
const routePermissions = await ctx.db.getRepository("rolesMultiPortalDesktopRoutes").find({
|
|
1358
|
+
const routePermissions = await ctx.db.getRepository(portalContext.relation === "uiLayouts" ? "rolesDesktopRoutes" : "rolesMultiPortalDesktopRoutes").find({
|
|
1375
1359
|
fields: ["desktopRouteId"],
|
|
1376
1360
|
filter: {
|
|
1377
1361
|
roleName: currentRoles,
|
|
1378
|
-
multiPortalUid
|
|
1362
|
+
...portalContext.relation === "multiPortals" ? { multiPortalUid: portalContext.portalUid } : {}
|
|
1379
1363
|
}
|
|
1380
1364
|
});
|
|
1381
1365
|
const routeIds = /* @__PURE__ */ new Set();
|
|
@@ -1391,7 +1375,7 @@ async function getMultiPortalAccessibleRouteIds(ctx, multiPortalUid) {
|
|
|
1391
1375
|
const portalRoutes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1392
1376
|
fields: ["id"],
|
|
1393
1377
|
filter: {
|
|
1394
|
-
...
|
|
1378
|
+
...portalContext.filter,
|
|
1395
1379
|
id: Array.from(routeIds)
|
|
1396
1380
|
}
|
|
1397
1381
|
});
|
|
@@ -1421,9 +1405,107 @@ function setDesktopRouteChildren(route, children) {
|
|
|
1421
1405
|
const maybeModel = route;
|
|
1422
1406
|
if (typeof maybeModel.setDataValue === "function") {
|
|
1423
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"];
|
|
1424
1416
|
return;
|
|
1425
1417
|
}
|
|
1426
|
-
|
|
1418
|
+
if (!maybeModel._options.includeNames.includes("children")) {
|
|
1419
|
+
maybeModel._options.includeNames.push("children");
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
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);
|
|
1435
|
+
}
|
|
1436
|
+
const children = getRecordField(route, "children");
|
|
1437
|
+
if (Array.isArray(children)) {
|
|
1438
|
+
collectDesktopRouteStringIds(children, routeIds);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
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);
|
|
1427
1509
|
}
|
|
1428
1510
|
function removeDesktopRoutesByIds(routes, routeIds) {
|
|
1429
1511
|
return routes.map((route) => {
|
|
@@ -1490,23 +1572,26 @@ async function removeMultiPortalOwnedRouteFromGetResponse(ctx) {
|
|
|
1490
1572
|
ctx.body = void 0;
|
|
1491
1573
|
}
|
|
1492
1574
|
async function replaceListAccessibleRoutesWithPortalScopedRoutes(ctx, portalContext) {
|
|
1493
|
-
const routeIds = await getMultiPortalAccessibleRouteIds(ctx, portalContext
|
|
1575
|
+
const routeIds = await getMultiPortalAccessibleRouteIds(ctx, portalContext);
|
|
1494
1576
|
if (routeIds && routeIds.size === 0) {
|
|
1495
1577
|
ctx.body = [];
|
|
1496
1578
|
return;
|
|
1497
1579
|
}
|
|
1498
|
-
|
|
1580
|
+
const routes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1499
1581
|
tree: true,
|
|
1500
1582
|
sort: "sort",
|
|
1501
1583
|
filter: {
|
|
1502
|
-
...
|
|
1584
|
+
...portalContext.filter,
|
|
1503
1585
|
...routeIds ? { id: Array.from(routeIds) } : {}
|
|
1504
1586
|
}
|
|
1505
1587
|
});
|
|
1588
|
+
ctx.body = removeNestedRootDesktopRoutes(
|
|
1589
|
+
await includeDesktopRouteAncestorsForListAccessible(ctx, routes, portalContext.filter)
|
|
1590
|
+
);
|
|
1506
1591
|
}
|
|
1507
1592
|
async function replaceGetAccessibleRouteWithPortalScopedRoute(ctx, portalContext) {
|
|
1508
1593
|
var _a;
|
|
1509
|
-
const routeIds = await getMultiPortalAccessibleRouteIds(ctx, portalContext
|
|
1594
|
+
const routeIds = await getMultiPortalAccessibleRouteIds(ctx, portalContext);
|
|
1510
1595
|
if (routeIds && routeIds.size === 0) {
|
|
1511
1596
|
ctx.status = 204;
|
|
1512
1597
|
ctx.body = void 0;
|
|
@@ -1516,7 +1601,7 @@ async function replaceGetAccessibleRouteWithPortalScopedRoute(ctx, portalContext
|
|
|
1516
1601
|
sort: "sort",
|
|
1517
1602
|
filterByTk: (_a = ctx.action) == null ? void 0 : _a.params.filterByTk,
|
|
1518
1603
|
filter: {
|
|
1519
|
-
...
|
|
1604
|
+
...portalContext.filter,
|
|
1520
1605
|
...routeIds ? { id: Array.from(routeIds) } : {}
|
|
1521
1606
|
}
|
|
1522
1607
|
});
|
|
@@ -1553,13 +1638,10 @@ async function addMultiPortalListAccessibleGuard(ctx, next) {
|
|
|
1553
1638
|
return;
|
|
1554
1639
|
}
|
|
1555
1640
|
await next();
|
|
1556
|
-
if (
|
|
1641
|
+
if (portalContext) {
|
|
1557
1642
|
await replaceListAccessibleRoutesWithPortalScopedRoutes(ctx, portalContext);
|
|
1558
1643
|
return;
|
|
1559
1644
|
}
|
|
1560
|
-
if ((portalContext == null ? void 0 : portalContext.relation) === "uiLayouts") {
|
|
1561
|
-
return;
|
|
1562
|
-
}
|
|
1563
1645
|
await removeMultiPortalOwnedRoutesFromListResponse(ctx);
|
|
1564
1646
|
}
|
|
1565
1647
|
async function addMultiPortalGetAccessibleGuard(ctx, next) {
|
|
@@ -1570,17 +1652,13 @@ async function addMultiPortalGetAccessibleGuard(ctx, next) {
|
|
|
1570
1652
|
return;
|
|
1571
1653
|
}
|
|
1572
1654
|
await next();
|
|
1573
|
-
if (
|
|
1655
|
+
if (portalContext) {
|
|
1574
1656
|
await replaceGetAccessibleRouteWithPortalScopedRoute(ctx, portalContext);
|
|
1575
1657
|
return;
|
|
1576
1658
|
}
|
|
1577
|
-
if ((portalContext == null ? void 0 : portalContext.relation) === "uiLayouts") {
|
|
1578
|
-
return;
|
|
1579
|
-
}
|
|
1580
1659
|
await removeMultiPortalOwnedRouteFromGetResponse(ctx);
|
|
1581
1660
|
}
|
|
1582
1661
|
async function mapMultiPortalLayoutToUiLayoutForRolePermissionTargets(ctx, next) {
|
|
1583
|
-
var _a;
|
|
1584
1662
|
const portalRequest = await findRequestedMultiPortal(ctx);
|
|
1585
1663
|
if (portalRequest.requested && !portalRequest.portal) {
|
|
1586
1664
|
ctx.status = 200;
|
|
@@ -1588,22 +1666,19 @@ async function mapMultiPortalLayoutToUiLayoutForRolePermissionTargets(ctx, next)
|
|
|
1588
1666
|
return;
|
|
1589
1667
|
}
|
|
1590
1668
|
const scope = portalRequest.scope;
|
|
1591
|
-
if (
|
|
1592
|
-
if ((_a = ctx.action) == null ? void 0 : _a.params) {
|
|
1593
|
-
ctx.action.params.layout = scope.uiLayoutUid;
|
|
1594
|
-
}
|
|
1595
|
-
await next();
|
|
1596
|
-
return;
|
|
1597
|
-
}
|
|
1598
|
-
if ((scope == null ? void 0 : scope.relation) === "multiPortals") {
|
|
1669
|
+
if (scope) {
|
|
1599
1670
|
const routes = await ctx.db.getRepository("desktopRoutes").find({
|
|
1600
|
-
tree: true,
|
|
1601
1671
|
sort: "sort",
|
|
1602
1672
|
filter: scope.filter,
|
|
1603
1673
|
fields: [...DESKTOP_ROUTE_ROLE_PERMISSION_TARGET_FIELDS]
|
|
1604
1674
|
});
|
|
1675
|
+
const routeIds = /* @__PURE__ */ new Set();
|
|
1676
|
+
collectDesktopRouteStringIds(routes, routeIds);
|
|
1677
|
+
const routeTree = buildAccessibleDesktopRouteTreeWithAncestors(routes, routeIds);
|
|
1605
1678
|
ctx.status = 200;
|
|
1606
|
-
ctx.body =
|
|
1679
|
+
ctx.body = removeNestedRootDesktopRoutes(routeTree).map(
|
|
1680
|
+
(route) => pickDesktopRouteRolePermissionTargetFields(route)
|
|
1681
|
+
);
|
|
1607
1682
|
return;
|
|
1608
1683
|
}
|
|
1609
1684
|
await next();
|
|
@@ -1691,7 +1766,7 @@ async function findDesktopRouteMutationTargets(ctx, scopeFilter, transaction, se
|
|
|
1691
1766
|
...filter ?? {},
|
|
1692
1767
|
...scopeFilter ?? {}
|
|
1693
1768
|
},
|
|
1694
|
-
...transaction ? { lock: transaction.LOCK.UPDATE } : {},
|
|
1769
|
+
...transaction && !scopeFilter ? { lock: transaction.LOCK.UPDATE } : {},
|
|
1695
1770
|
transaction
|
|
1696
1771
|
});
|
|
1697
1772
|
}
|
|
@@ -1843,7 +1918,6 @@ async function guardDesktopRouteMoveScope(ctx, next) {
|
|
|
1843
1918
|
id: routeIds,
|
|
1844
1919
|
...scope.filter
|
|
1845
1920
|
},
|
|
1846
|
-
lock: transaction.LOCK.UPDATE,
|
|
1847
1921
|
transaction
|
|
1848
1922
|
});
|
|
1849
1923
|
if (normalizeDesktopRouteTargetIds(allRoutes).join(",") !== normalizeDesktopRouteTargetIds(scopedRoutes).join(",") || scopedRoutes.length !== routeIds.length) {
|
|
@@ -1873,7 +1947,6 @@ async function guardDesktopRouteMoveScope(ctx, next) {
|
|
|
1873
1947
|
id: parentIds,
|
|
1874
1948
|
...scope.filter
|
|
1875
1949
|
},
|
|
1876
|
-
lock: transaction.LOCK.UPDATE,
|
|
1877
1950
|
transaction
|
|
1878
1951
|
});
|
|
1879
1952
|
if (normalizeDesktopRouteTargetIds(parents).join(",") !== parentIds.map(String).sort().join(",")) {
|
|
@@ -1921,7 +1994,6 @@ async function guardDesktopRouteMoveScope(ctx, next) {
|
|
|
1921
1994
|
$gte: targetSort
|
|
1922
1995
|
}
|
|
1923
1996
|
},
|
|
1924
|
-
lock: transaction.LOCK.UPDATE,
|
|
1925
1997
|
transaction
|
|
1926
1998
|
});
|
|
1927
1999
|
const affectedRouteIds = affectedRoutes.map((route) => route.get("id"));
|
|
@@ -2019,7 +2091,7 @@ async function findDesktopRouteDestroyRoots(ctx, transaction, scopeFilter) {
|
|
|
2019
2091
|
...scopeFilter ?? {},
|
|
2020
2092
|
...filterByTk.length ? { id: filterByTk } : {}
|
|
2021
2093
|
},
|
|
2022
|
-
lock: transaction.LOCK.UPDATE,
|
|
2094
|
+
...scopeFilter ? {} : { lock: transaction.LOCK.UPDATE },
|
|
2023
2095
|
transaction
|
|
2024
2096
|
});
|
|
2025
2097
|
return collectDesktopRouteIds(records);
|
|
@@ -2085,13 +2157,20 @@ async function detachDesktopRouteTreeOwner(ctx, routeIds, scope, transaction) {
|
|
|
2085
2157
|
}
|
|
2086
2158
|
async function detachOwnerAndFindDestroyRoots(ctx, routeIds, scope, transaction) {
|
|
2087
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
|
+
});
|
|
2088
2168
|
const routes = await ctx.db.getRepository("desktopRoutes").find({
|
|
2089
2169
|
fields: ["id", "parentId"],
|
|
2090
2170
|
appends: ["multiPortals", "uiLayouts"],
|
|
2091
2171
|
filter: {
|
|
2092
2172
|
id: routeIds
|
|
2093
2173
|
},
|
|
2094
|
-
lock: transaction.LOCK.UPDATE,
|
|
2095
2174
|
transaction
|
|
2096
2175
|
});
|
|
2097
2176
|
const routesById = new Map(routes.map((route) => [String(route.get("id")), route]));
|
|
@@ -2187,24 +2266,119 @@ async function listEnabledMultiPortals(ctx, next) {
|
|
|
2187
2266
|
const records = await ctx.db.getRepository("multiPortals").find({
|
|
2188
2267
|
filter: {
|
|
2189
2268
|
enabled: true,
|
|
2190
|
-
|
|
2269
|
+
uiLayoutUid: {
|
|
2270
|
+
$in: [...import_constants.MULTI_PORTAL_UI_LAYOUT_UIDS]
|
|
2271
|
+
}
|
|
2191
2272
|
},
|
|
2192
|
-
fields: [...
|
|
2193
|
-
appends: ["uiLayout"],
|
|
2273
|
+
fields: [...MULTI_PORTAL_RUNTIME_FIELDS],
|
|
2194
2274
|
sort: ["uid"]
|
|
2195
2275
|
});
|
|
2196
2276
|
ctx.body = records.map((record) => pickMultiPortalRuntimeFields(record));
|
|
2197
2277
|
await next();
|
|
2198
2278
|
}
|
|
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";
|
|
2284
|
+
}
|
|
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"))
|
|
2292
|
+
};
|
|
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;
|
|
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
|
+
}
|
|
2199
2372
|
async function listAccessibleMultiPortals(ctx, next) {
|
|
2200
2373
|
const accessiblePortalUids = await listCurrentRoleAccessibleMultiPortalUids(ctx);
|
|
2201
2374
|
const records = await ctx.db.getRepository("multiPortals").find({
|
|
2202
2375
|
filter: {
|
|
2203
2376
|
enabled: true,
|
|
2204
|
-
|
|
2377
|
+
uiLayoutUid: {
|
|
2378
|
+
$in: [...import_constants.MULTI_PORTAL_UI_LAYOUT_UIDS]
|
|
2379
|
+
}
|
|
2205
2380
|
},
|
|
2206
|
-
fields: [...
|
|
2207
|
-
appends: ["uiLayout"],
|
|
2381
|
+
fields: [...MULTI_PORTAL_ACCESSIBLE_FIELDS],
|
|
2208
2382
|
sort: ["uid"]
|
|
2209
2383
|
});
|
|
2210
2384
|
const accessiblePortalUidSet = Array.isArray(accessiblePortalUids) ? new Set(accessiblePortalUids) : void 0;
|
|
@@ -2343,11 +2517,15 @@ async function grantDefaultAccessToNewMultiPortal(db, multiPortal, options) {
|
|
|
2343
2517
|
class PluginMultiPortalServer extends import_server.Plugin {
|
|
2344
2518
|
portalStorageTaskKeys = /* @__PURE__ */ new Set();
|
|
2345
2519
|
portalStorageTasks = /* @__PURE__ */ new Map();
|
|
2520
|
+
deprecatedInitPortalEnvWarningEmitted = false;
|
|
2346
2521
|
async afterAdd() {
|
|
2347
2522
|
}
|
|
2348
2523
|
getAppName() {
|
|
2349
2524
|
return this.app.name || MAIN_APP_NAME;
|
|
2350
2525
|
}
|
|
2526
|
+
getCurrentStorageAppName() {
|
|
2527
|
+
return normalizePortalStorageName(this.getAppName()) || MAIN_APP_NAME;
|
|
2528
|
+
}
|
|
2351
2529
|
getMultiPortalStorageItem(multiPortal, previous = false) {
|
|
2352
2530
|
const record = multiPortal;
|
|
2353
2531
|
const readField = (field) => previous && typeof record.previous === "function" ? record.previous(field) : getRecordField(record, field);
|
|
@@ -2362,8 +2540,7 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2362
2540
|
return {
|
|
2363
2541
|
appName: this.getAppName(),
|
|
2364
2542
|
portalName,
|
|
2365
|
-
enabled: readField("enabled") === true
|
|
2366
|
-
config: getPortalStorageConfig(readField("options"))
|
|
2543
|
+
enabled: readField("enabled") === true
|
|
2367
2544
|
};
|
|
2368
2545
|
}
|
|
2369
2546
|
warnPortalStorageSyncFailed(error) {
|
|
@@ -2373,6 +2550,14 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2373
2550
|
error
|
|
2374
2551
|
});
|
|
2375
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
|
+
}
|
|
2376
2561
|
getPortalStorageTaskKey(item) {
|
|
2377
2562
|
return `${item.appName}/${item.portalName}`;
|
|
2378
2563
|
}
|
|
@@ -2391,10 +2576,9 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2391
2576
|
}
|
|
2392
2577
|
await run();
|
|
2393
2578
|
}
|
|
2394
|
-
async
|
|
2395
|
-
await import_fs.default.promises.rm((0, import_utils.storagePathJoin)("portals", item.appName, item.portalName, "dist"), {
|
|
2396
|
-
force: true
|
|
2397
|
-
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
|
|
2398
2582
|
});
|
|
2399
2583
|
}
|
|
2400
2584
|
waitForPortalStorageTasks() {
|
|
@@ -2404,10 +2588,11 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2404
2588
|
}
|
|
2405
2589
|
return Promise.allSettled(tasks).then(() => void 0);
|
|
2406
2590
|
}
|
|
2407
|
-
async schedulePortalTemplateCopyAndBuild(item, template, portalDir) {
|
|
2591
|
+
async schedulePortalTemplateCopyAndBuild(item, template, portalDir, options) {
|
|
2408
2592
|
var _a;
|
|
2409
2593
|
const taskKey = this.getPortalStorageTaskKey(item);
|
|
2410
2594
|
if (this.portalStorageTaskKeys.has(taskKey)) {
|
|
2595
|
+
this.logPortalBuildHtml(item, "skipped", "a storage task is already running");
|
|
2411
2596
|
await ((_a = template.cleanup) == null ? void 0 : _a.call(template));
|
|
2412
2597
|
return;
|
|
2413
2598
|
}
|
|
@@ -2428,14 +2613,13 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2428
2613
|
await copyPortalTemplate(template.dir, portalDir);
|
|
2429
2614
|
await appendPortalStorageLog(logPath, `Default portal template copied to ${portalDir}.`);
|
|
2430
2615
|
}
|
|
2431
|
-
await ensurePortalStorageConfigFiles(portalDir, item);
|
|
2432
|
-
await appendPortalStorageLog(logPath, `Portal configuration files updated in ${portalDir}.`);
|
|
2433
2616
|
if (item.enabled) {
|
|
2434
|
-
|
|
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");
|
|
2435
2620
|
return;
|
|
2436
2621
|
}
|
|
2437
|
-
|
|
2438
|
-
await appendPortalStorageLog(logPath, `Portal dist directory removed for ${item.appName}/${item.portalName}.`);
|
|
2622
|
+
this.logPortalBuildHtml(item, "skipped", "the portal is disabled");
|
|
2439
2623
|
} catch (error) {
|
|
2440
2624
|
await appendPortalStorageLog(
|
|
2441
2625
|
logPath,
|
|
@@ -2451,32 +2635,41 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2451
2635
|
this.portalStorageTasks.set(taskKey, task);
|
|
2452
2636
|
await task;
|
|
2453
2637
|
}
|
|
2454
|
-
async ensurePortalStorageItem(item) {
|
|
2638
|
+
async ensurePortalStorageItem(item, options = {}) {
|
|
2455
2639
|
const portalDir = (0, import_utils.storagePathJoin)("portals", item.appName, item.portalName);
|
|
2456
2640
|
const portalIndex = import_path.default.join(portalDir, "dist", "index.html");
|
|
2641
|
+
const logPath = getPortalStorageLogPath(item);
|
|
2457
2642
|
if (!await pathExists(portalDir)) {
|
|
2458
|
-
const template = await resolvePortalTemplate(getInitPortalTemplate(),
|
|
2459
|
-
await this.schedulePortalTemplateCopyAndBuild(item, template, portalDir);
|
|
2643
|
+
const template = await resolvePortalTemplate(getInitPortalTemplate(), logPath);
|
|
2644
|
+
await this.schedulePortalTemplateCopyAndBuild(item, template, portalDir, options);
|
|
2460
2645
|
return;
|
|
2461
2646
|
}
|
|
2462
|
-
await ensurePortalStorageConfigFiles(portalDir, item);
|
|
2463
2647
|
if (item.enabled) {
|
|
2464
|
-
|
|
2465
|
-
|
|
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");
|
|
2466
2659
|
}
|
|
2467
2660
|
return;
|
|
2468
2661
|
}
|
|
2469
|
-
|
|
2662
|
+
this.logPortalBuildHtml(item, "skipped", "the portal is disabled");
|
|
2470
2663
|
}
|
|
2471
|
-
async syncMultiPortalStorageItem(multiPortal, options, syncPrevious = false) {
|
|
2664
|
+
async syncMultiPortalStorageItem(multiPortal, options, syncPrevious = false, forceBuild = false) {
|
|
2472
2665
|
const currentItem = this.getMultiPortalStorageItem(multiPortal);
|
|
2473
2666
|
const previousItem = syncPrevious ? this.getMultiPortalStorageItem(multiPortal, true) : null;
|
|
2474
2667
|
await this.runPortalStorageTask(async () => {
|
|
2475
|
-
if (previousItem && (!currentItem || previousItem.appName !== currentItem.appName || previousItem.portalName !== currentItem.portalName
|
|
2476
|
-
await this.
|
|
2668
|
+
if (previousItem && (!currentItem || previousItem.appName !== currentItem.appName || previousItem.portalName !== currentItem.portalName)) {
|
|
2669
|
+
await this.removePortalStorageIndexHtml(previousItem);
|
|
2477
2670
|
}
|
|
2478
2671
|
if (currentItem) {
|
|
2479
|
-
await this.ensurePortalStorageItem(currentItem);
|
|
2672
|
+
await this.ensurePortalStorageItem(currentItem, { ...options, forceBuild });
|
|
2480
2673
|
}
|
|
2481
2674
|
}, options);
|
|
2482
2675
|
}
|
|
@@ -2519,12 +2712,12 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2519
2712
|
await next();
|
|
2520
2713
|
}
|
|
2521
2714
|
async deployPortalDist(ctx, next) {
|
|
2522
|
-
var _a, _b, _c
|
|
2715
|
+
var _a, _b, _c;
|
|
2523
2716
|
const deployCtx = ctx;
|
|
2524
|
-
const appName =
|
|
2525
|
-
const portalName = normalizePortalStorageName((
|
|
2526
|
-
const basePath = trimString((
|
|
2527
|
-
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);
|
|
2528
2721
|
try {
|
|
2529
2722
|
if (!isValidPortalDeploySegment(appName)) {
|
|
2530
2723
|
throw new Error("Invalid app");
|
|
@@ -2561,9 +2754,9 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2561
2754
|
}
|
|
2562
2755
|
}
|
|
2563
2756
|
async pullPortalSource(ctx, next) {
|
|
2564
|
-
var _a
|
|
2565
|
-
const appName =
|
|
2566
|
-
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);
|
|
2567
2760
|
try {
|
|
2568
2761
|
if (!isValidPortalDeploySegment(appName)) {
|
|
2569
2762
|
throw new Error("Invalid app");
|
|
@@ -2583,11 +2776,11 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2583
2776
|
}
|
|
2584
2777
|
}
|
|
2585
2778
|
async pushPortalSource(ctx, next) {
|
|
2586
|
-
var _a, _b
|
|
2779
|
+
var _a, _b;
|
|
2587
2780
|
const sourceCtx = ctx;
|
|
2588
|
-
const appName =
|
|
2589
|
-
const portalName = normalizePortalStorageName((
|
|
2590
|
-
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);
|
|
2591
2784
|
try {
|
|
2592
2785
|
if (!isValidPortalDeploySegment(appName)) {
|
|
2593
2786
|
throw new Error("Invalid app");
|
|
@@ -2641,38 +2834,20 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2641
2834
|
const records = await this.db.getRepository("multiPortals").find({
|
|
2642
2835
|
filter: {
|
|
2643
2836
|
enabled: true,
|
|
2644
|
-
|
|
2837
|
+
uiLayoutUid: {
|
|
2838
|
+
$in: [...import_constants.MULTI_PORTAL_UI_LAYOUT_UIDS]
|
|
2839
|
+
}
|
|
2645
2840
|
},
|
|
2646
|
-
fields: [...
|
|
2647
|
-
appends: ["uiLayout"],
|
|
2841
|
+
fields: [...MULTI_PORTAL_ACCESSIBLE_FIELDS],
|
|
2648
2842
|
sort: ["uid"],
|
|
2649
2843
|
transaction: options == null ? void 0 : options.transaction
|
|
2650
2844
|
});
|
|
2651
|
-
return records.
|
|
2652
|
-
const
|
|
2653
|
-
|
|
2654
|
-
const uid = typeof portal.uid === "string" ? portal.uid : "";
|
|
2655
|
-
const title = typeof portal.title === "string" ? portal.title : "";
|
|
2656
|
-
return {
|
|
2657
|
-
uid,
|
|
2658
|
-
title,
|
|
2659
|
-
icon: typeof portal.icon === "string" ? portal.icon : null,
|
|
2660
|
-
portalType: typeof portal.portalType === "string" ? portal.portalType : null,
|
|
2661
|
-
routePath: String(portal.routePath || ""),
|
|
2662
|
-
layout: typeof (uiLayout == null ? void 0 : uiLayout.layoutType) === "string" ? uiLayout.layoutType : null
|
|
2663
|
-
};
|
|
2845
|
+
return records.flatMap((record) => {
|
|
2846
|
+
const item = this.toAppPortalManifestItem(record);
|
|
2847
|
+
return item ? [item] : [];
|
|
2664
2848
|
});
|
|
2665
2849
|
}
|
|
2666
|
-
|
|
2667
|
-
if (typeof uid !== "string" || !uid) {
|
|
2668
|
-
return null;
|
|
2669
|
-
}
|
|
2670
|
-
return this.db.getRepository("uiLayouts").findOne({
|
|
2671
|
-
filterByTk: uid,
|
|
2672
|
-
transaction: options == null ? void 0 : options.transaction
|
|
2673
|
-
});
|
|
2674
|
-
}
|
|
2675
|
-
async toAppPortalManifestItem(multiPortal, options) {
|
|
2850
|
+
toAppPortalManifestItem(multiPortal) {
|
|
2676
2851
|
const uid = getRecordField(multiPortal, "uid");
|
|
2677
2852
|
const title = getRecordField(multiPortal, "title");
|
|
2678
2853
|
const portalType = getRecordField(multiPortal, "portalType");
|
|
@@ -2682,19 +2857,18 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2682
2857
|
return null;
|
|
2683
2858
|
}
|
|
2684
2859
|
const uiLayoutUid = getRecordField(multiPortal, "uiLayoutUid");
|
|
2685
|
-
const
|
|
2686
|
-
if (!
|
|
2860
|
+
const layout = (0, import_constants.getMultiPortalLayoutType)(uiLayoutUid);
|
|
2861
|
+
if (!layout) {
|
|
2687
2862
|
return null;
|
|
2688
2863
|
}
|
|
2689
2864
|
const icon = getRecordField(multiPortal, "icon");
|
|
2690
|
-
const layoutType = getRecordField(uiLayout, "layoutType");
|
|
2691
2865
|
return {
|
|
2692
2866
|
uid,
|
|
2693
2867
|
title,
|
|
2694
2868
|
icon: typeof icon === "string" ? icon : null,
|
|
2695
2869
|
portalType: typeof portalType === "string" ? portalType : null,
|
|
2696
2870
|
routePath,
|
|
2697
|
-
layout
|
|
2871
|
+
layout
|
|
2698
2872
|
};
|
|
2699
2873
|
}
|
|
2700
2874
|
async setAppManifestItem(item, options) {
|
|
@@ -2737,30 +2911,13 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2737
2911
|
if (typeof uid !== "string" || !uid) {
|
|
2738
2912
|
return;
|
|
2739
2913
|
}
|
|
2740
|
-
const item =
|
|
2914
|
+
const item = this.toAppPortalManifestItem(multiPortal);
|
|
2741
2915
|
if (item) {
|
|
2742
2916
|
await this.setAppManifestItem(item, options);
|
|
2743
2917
|
return;
|
|
2744
2918
|
}
|
|
2745
2919
|
await this.removeAppManifestItem(uid, options);
|
|
2746
2920
|
}
|
|
2747
|
-
async publishUiLayoutManifestItems(uiLayout, options) {
|
|
2748
|
-
const uiLayoutUid = getRecordField(uiLayout, "uid");
|
|
2749
|
-
if (typeof uiLayoutUid !== "string" || !uiLayoutUid) {
|
|
2750
|
-
return;
|
|
2751
|
-
}
|
|
2752
|
-
const records = await this.db.getRepository("multiPortals").find({
|
|
2753
|
-
filter: {
|
|
2754
|
-
uiLayoutUid
|
|
2755
|
-
},
|
|
2756
|
-
fields: [...MULTI_PORTAL_ACCESSIBLE_QUERY_FIELDS],
|
|
2757
|
-
transaction: options == null ? void 0 : options.transaction
|
|
2758
|
-
});
|
|
2759
|
-
for (const record of records) {
|
|
2760
|
-
record.set("uiLayout", uiLayout);
|
|
2761
|
-
await this.publishAppManifestItem(record, options);
|
|
2762
|
-
}
|
|
2763
|
-
}
|
|
2764
2921
|
async reconcilePortalStorage(options) {
|
|
2765
2922
|
const records = await this.db.getRepository("multiPortals").find({
|
|
2766
2923
|
filter: {
|
|
@@ -2812,6 +2969,7 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2812
2969
|
}
|
|
2813
2970
|
async beforeLoad() {
|
|
2814
2971
|
this.app.db.registerRepositories({ MultiPortalDesktopRouteRepository });
|
|
2972
|
+
this.app.resourceManager.registerPreActionHandler("roles:check", checkMultiPortalAccessForRolesCheck);
|
|
2815
2973
|
this.app.resourceManager.registerPreActionHandler("desktopRoutes:list", addDesktopRouteEffectiveScopeFilter);
|
|
2816
2974
|
this.app.resourceManager.registerPreActionHandler("desktopRoutes:get", addDesktopRouteEffectiveScopeFilter);
|
|
2817
2975
|
this.app.resourceManager.registerPreActionHandler(
|
|
@@ -2823,26 +2981,30 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2823
2981
|
"desktopRoutes:listRolePermissionTargets",
|
|
2824
2982
|
mapMultiPortalLayoutToUiLayoutForRolePermissionTargets
|
|
2825
2983
|
);
|
|
2984
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:create", preventDirectDefaultPortalMutation);
|
|
2985
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:create", validateMultiPortalUiLayoutUidWrite);
|
|
2826
2986
|
this.app.resourceManager.registerPreActionHandler("multiPortals:create", captureSkipCreatePortalDirectory);
|
|
2827
2987
|
this.app.resourceManager.registerPreActionHandler("multiPortals:create", normalizeMultiPortalSlugValues);
|
|
2828
|
-
this.app.resourceManager.registerPreActionHandler("multiPortals:
|
|
2829
|
-
this.app.resourceManager.registerPreActionHandler("multiPortals:update",
|
|
2988
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:update", preventDirectDefaultPortalMutation);
|
|
2989
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:update", validateMultiPortalUiLayoutUidWrite);
|
|
2830
2990
|
this.app.resourceManager.registerPreActionHandler("multiPortals:update", normalizeMultiPortalSlugValues);
|
|
2831
|
-
this.app.resourceManager.registerPreActionHandler("multiPortals:
|
|
2991
|
+
this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", preventDirectDefaultPortalMutation);
|
|
2832
2992
|
this.app.resourceManager.registerPreActionHandler(
|
|
2833
2993
|
"multiPortals:firstOrCreate",
|
|
2834
|
-
|
|
2994
|
+
validateMultiPortalUiLayoutUidWrite
|
|
2835
2995
|
);
|
|
2836
2996
|
this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", captureSkipCreatePortalDirectory);
|
|
2837
2997
|
this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", normalizeMultiPortalSlugValues);
|
|
2838
|
-
this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", preventUiLayoutRouteNameConflict);
|
|
2839
2998
|
this.app.resourceManager.registerPreActionHandler(
|
|
2840
2999
|
"multiPortals:updateOrCreate",
|
|
2841
|
-
|
|
3000
|
+
preventDirectDefaultPortalMutation
|
|
3001
|
+
);
|
|
3002
|
+
this.app.resourceManager.registerPreActionHandler(
|
|
3003
|
+
"multiPortals:updateOrCreate",
|
|
3004
|
+
validateMultiPortalUiLayoutUidWrite
|
|
2842
3005
|
);
|
|
2843
3006
|
this.app.resourceManager.registerPreActionHandler("multiPortals:updateOrCreate", captureSkipCreatePortalDirectory);
|
|
2844
3007
|
this.app.resourceManager.registerPreActionHandler("multiPortals:updateOrCreate", normalizeMultiPortalSlugValues);
|
|
2845
|
-
this.app.resourceManager.registerPreActionHandler("multiPortals:updateOrCreate", preventUiLayoutRouteNameConflict);
|
|
2846
3008
|
this.app.resourceManager.registerPreActionHandler("desktopRoutes:create", addDesktopRouteCreateMultiPortal, {
|
|
2847
3009
|
after: UI_LAYOUT_DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG
|
|
2848
3010
|
});
|
|
@@ -2867,12 +3029,15 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2867
3029
|
actions: ROLE_MULTI_PORTAL_PERMISSION_ACTIONS
|
|
2868
3030
|
});
|
|
2869
3031
|
this.app.acl.allow("multiPortals", "listEnabled", "public");
|
|
3032
|
+
this.app.acl.allow("multiPortals", "getDefault", "public");
|
|
2870
3033
|
this.app.acl.allow("multiPortals", "listAccessible", "loggedIn");
|
|
2871
3034
|
this.app.resourceManager.use(createPortalDeployUploadMiddleware(), {
|
|
2872
3035
|
tag: "multiPortalDeployUpload",
|
|
2873
3036
|
after: "acl"
|
|
2874
3037
|
});
|
|
2875
3038
|
this.app.resourceManager.registerActionHandler("multiPortals:listEnabled", listEnabledMultiPortals);
|
|
3039
|
+
this.app.resourceManager.registerActionHandler("multiPortals:getDefault", getDefaultMultiPortal);
|
|
3040
|
+
this.app.resourceManager.registerActionHandler("multiPortals:setDefault", setDefaultMultiPortal);
|
|
2876
3041
|
this.app.resourceManager.registerActionHandler("multiPortals:listAccessible", listAccessibleMultiPortals);
|
|
2877
3042
|
this.app.resourceManager.registerActionHandler("multiPortals:deploy", async (ctx, next) => {
|
|
2878
3043
|
await this.deployPortalDist(ctx, next);
|
|
@@ -2897,13 +3062,18 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2897
3062
|
(0, import_ensureDefaultRoleMultiPortalAccess.applyDefaultRoleMultiPortalAccess)(role);
|
|
2898
3063
|
});
|
|
2899
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
|
+
});
|
|
2900
3070
|
this.app.db.on("multiPortals.afterCreate", async (multiPortal, options) => {
|
|
2901
3071
|
await grantDefaultAccessToNewMultiPortal(this.app.db, multiPortal, options);
|
|
2902
3072
|
await this.publishAppManifestItem(multiPortal, options);
|
|
2903
3073
|
if (shouldSkipCreatePortalDirectory(options)) {
|
|
2904
3074
|
return;
|
|
2905
3075
|
}
|
|
2906
|
-
await this.syncMultiPortalStorageItem(multiPortal, options);
|
|
3076
|
+
await this.syncMultiPortalStorageItem(multiPortal, options, false, true);
|
|
2907
3077
|
});
|
|
2908
3078
|
this.app.db.on("multiPortals.afterUpdate", async (multiPortal, options) => {
|
|
2909
3079
|
await this.publishAppManifestItem(multiPortal, options);
|
|
@@ -2916,11 +3086,16 @@ class PluginMultiPortalServer extends import_server.Plugin {
|
|
|
2916
3086
|
}
|
|
2917
3087
|
await this.removeMultiPortalStorageItem(multiPortal, options);
|
|
2918
3088
|
});
|
|
2919
|
-
this.app.db.on("uiLayouts.afterUpdate", async (uiLayout, options) => {
|
|
2920
|
-
await this.publishUiLayoutManifestItems(uiLayout, options);
|
|
2921
|
-
});
|
|
2922
3089
|
}
|
|
2923
3090
|
async install() {
|
|
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
|
+
}
|
|
2924
3099
|
const version = await this.app.version.get();
|
|
2925
3100
|
if (!version) {
|
|
2926
3101
|
await (0, import_ensureDefaultRoleMultiPortalAccess.ensureDefaultRoleMultiPortalAccess)(this.db);
|