@nocobase/plugin-multi-portal 3.0.0-alpha.4 → 3.0.0-alpha.5

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.
@@ -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,10 +71,9 @@ 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";
@@ -181,7 +180,7 @@ function getDefaultMultiPortalRecord(options = {}) {
181
180
  authCheck: true,
182
181
  enabled: true,
183
182
  ...options.isDefault ? { isDefault: true } : {},
184
- uiLayoutUid: "admin-layout-model"
183
+ uiLayoutUid: import_constants.ADMIN_UI_LAYOUT_UID
185
184
  };
186
185
  }
187
186
  function getFreshMultiPortalRecords() {
@@ -198,7 +197,7 @@ function getFixedLayoutMultiPortalRecords() {
198
197
  routePath: "/admin",
199
198
  authCheck: true,
200
199
  enabled: true,
201
- uiLayoutUid: "admin-layout-model"
200
+ uiLayoutUid: import_constants.ADMIN_UI_LAYOUT_UID
202
201
  },
203
202
  {
204
203
  uid: import_constants.DEFAULT_MOBILE_MULTI_PORTAL_UID,
@@ -209,7 +208,7 @@ function getFixedLayoutMultiPortalRecords() {
209
208
  routePath: "/mobile",
210
209
  authCheck: true,
211
210
  enabled: true,
212
- uiLayoutUid: "mobile-layout-model"
211
+ uiLayoutUid: import_constants.MOBILE_UI_LAYOUT_UID
213
212
  }
214
213
  ];
215
214
  }
@@ -294,6 +293,13 @@ function getPortalDeployBasePath(appName, portalName) {
294
293
  const portalPath = appName === MAIN_APP_NAME ? `/${PORTAL_CLIENT_PREFIX}/${portalName}/` : `/${PORTAL_CLIENT_PREFIX}/apps/${appName}/${portalName}/`;
295
294
  return resolvePortalStoragePublicPath(joinPortalStoragePublicPath(process.env.APP_PUBLIC_PATH || "/", portalPath));
296
295
  }
296
+ function getPortalDeployBasePathCandidates(appName, portalName) {
297
+ const expectedBasePaths = [getPortalDeployBasePath(appName, portalName)];
298
+ if (appName !== MAIN_APP_NAME) {
299
+ expectedBasePaths.push(getPortalDeployBasePath(MAIN_APP_NAME, portalName));
300
+ }
301
+ return expectedBasePaths;
302
+ }
297
303
  function createPortalDeployUploadMiddleware() {
298
304
  const storage = import_utils.koaMulter.diskStorage({
299
305
  destination: import_os.default.tmpdir(),
@@ -620,9 +626,9 @@ function validatePortalDeployBasePath(appName, portalName, basePath) {
620
626
  if (normalizedBasePath.includes("..")) {
621
627
  throw new Error('basePath cannot contain ".."');
622
628
  }
623
- const expectedBasePath = getPortalDeployBasePath(appName, portalName);
624
- if (normalizedBasePath !== expectedBasePath) {
625
- throw new Error(`basePath must be ${expectedBasePath}`);
629
+ const expectedBasePaths = getPortalDeployBasePathCandidates(appName, portalName);
630
+ if (!expectedBasePaths.includes(normalizedBasePath)) {
631
+ throw new Error(`basePath must be ${expectedBasePaths.join(" or ")}`);
626
632
  }
627
633
  }
628
634
  function isPortalDeployTarEntry(entry) {
@@ -860,19 +866,11 @@ function collectDesktopRouteIds(record) {
860
866
  }
861
867
  return uniqueDesktopRouteIds(routeIds);
862
868
  }
863
- function pickMultiPortalUiLayoutRuntimeFields(record) {
864
- const result = {};
865
- for (const field of MULTI_PORTAL_UI_LAYOUT_RUNTIME_FIELDS) {
866
- result[field] = getRecordField(record, field);
867
- }
868
- return result;
869
- }
870
869
  function pickMultiPortalRuntimeFields(record) {
871
870
  const result = {};
872
871
  for (const field of MULTI_PORTAL_RUNTIME_FIELDS) {
873
872
  result[field] = getRecordField(record, field);
874
873
  }
875
- result.uiLayout = pickMultiPortalUiLayoutRuntimeFields(getRecordField(record, "uiLayout"));
876
874
  return result;
877
875
  }
878
876
  function pickMultiPortalAccessibleFields(record) {
@@ -880,7 +878,6 @@ function pickMultiPortalAccessibleFields(record) {
880
878
  for (const field of MULTI_PORTAL_ACCESSIBLE_FIELDS) {
881
879
  result[field] = getRecordField(record, field) ?? null;
882
880
  }
883
- result.uiLayout = pickMultiPortalUiLayoutRuntimeFields(getRecordField(record, "uiLayout"));
884
881
  return result;
885
882
  }
886
883
  function getExplicitRequestedLayoutUid(layout) {
@@ -1130,14 +1127,27 @@ async function seedHistoricalMultiPortals(db) {
1130
1127
  }
1131
1128
  await repairFixedLayoutMultiPortalRecords(db);
1132
1129
  }
1133
- async function preventMultiPortalBackingLayoutChange(ctx, next) {
1130
+ async function validateMultiPortalUiLayoutUidWrite(ctx, next) {
1131
+ var _a;
1134
1132
  const targets = await getMultiPortalWriteTargets(ctx, ["uiLayoutUid"]);
1133
+ const actionName = (_a = ctx.action) == null ? void 0 : _a.actionName;
1134
+ const createsWhenMissing = actionName === "create" || actionName === "firstOrCreate" || actionName === "updateOrCreate";
1135
1135
  for (const { existing, values } of targets) {
1136
- if (!Object.prototype.hasOwnProperty.call(values, "uiLayoutUid")) {
1136
+ const hasUiLayoutUid = Object.prototype.hasOwnProperty.call(values, "uiLayoutUid");
1137
+ if (!existing && createsWhenMissing && !hasUiLayoutUid) {
1138
+ ctx.throw(400, `Portal UI layout must be one of: ${import_constants.MULTI_PORTAL_UI_LAYOUT_UIDS.join(", ")}`);
1139
+ return;
1140
+ }
1141
+ if (!hasUiLayoutUid) {
1137
1142
  continue;
1138
1143
  }
1139
- if (existing && existing.get("uiLayoutUid") !== values.uiLayoutUid) {
1140
- ctx.throw(400, "Portal backing UI layout cannot be changed");
1144
+ if (!(0, import_constants.isMultiPortalUiLayoutUid)(values.uiLayoutUid)) {
1145
+ ctx.throw(400, `Portal UI layout must be one of: ${import_constants.MULTI_PORTAL_UI_LAYOUT_UIDS.join(", ")}`);
1146
+ return;
1147
+ }
1148
+ const existingUiLayoutUid = existing == null ? void 0 : existing.get("uiLayoutUid");
1149
+ if (existing && existingUiLayoutUid !== values.uiLayoutUid) {
1150
+ ctx.throw(400, "Portal UI layout cannot be changed");
1141
1151
  return;
1142
1152
  }
1143
1153
  }
@@ -1165,20 +1175,8 @@ async function findRequestedMultiPortal(ctx, transaction) {
1165
1175
  ctx.throw(400, `Portal '${portalUid}' does not support desktop routes`);
1166
1176
  }
1167
1177
  const uiLayoutUid = portal.get("uiLayoutUid");
1168
- if (typeof uiLayoutUid !== "string" || !uiLayoutUid) {
1169
- ctx.throw(400, `Portal '${portalUid}' has no backing UI layout`);
1170
- }
1171
- const uiLayout = await ctx.db.getRepository("uiLayouts").findOne({
1172
- filter: {
1173
- uid: uiLayoutUid,
1174
- enabled: true
1175
- },
1176
- fields: ["uid"],
1177
- ...transaction ? { lock: transaction.LOCK.UPDATE } : {},
1178
- transaction
1179
- });
1180
- if (!uiLayout) {
1181
- ctx.throw(400, `Portal '${portalUid}' has no enabled backing UI layout`);
1178
+ if (!(0, import_constants.isMultiPortalUiLayoutUid)(uiLayoutUid)) {
1179
+ ctx.throw(400, `Portal '${portalUid}' has an unsupported UI layout UID`);
1182
1180
  }
1183
1181
  const usesLayoutPermissions = (0, import_constants.isDefaultLayoutMultiPortalUid)(portalUid);
1184
1182
  return {
@@ -1363,7 +1361,7 @@ async function removeRouteIdsWithUnauthorizedAncestors(ctx, routeIds) {
1363
1361
  }
1364
1362
  }
1365
1363
  }
1366
- async function getMultiPortalAccessibleRouteIds(ctx, multiPortalUid) {
1364
+ async function getMultiPortalAccessibleRouteIds(ctx, portalContext) {
1367
1365
  const currentRoles = getCurrentRoles(ctx);
1368
1366
  if (currentRoles.includes("root")) {
1369
1367
  return;
@@ -1371,11 +1369,11 @@ async function getMultiPortalAccessibleRouteIds(ctx, multiPortalUid) {
1371
1369
  if (!currentRoles.length) {
1372
1370
  return /* @__PURE__ */ new Set();
1373
1371
  }
1374
- const routePermissions = await ctx.db.getRepository("rolesMultiPortalDesktopRoutes").find({
1372
+ const routePermissions = await ctx.db.getRepository(portalContext.relation === "uiLayouts" ? "rolesDesktopRoutes" : "rolesMultiPortalDesktopRoutes").find({
1375
1373
  fields: ["desktopRouteId"],
1376
1374
  filter: {
1377
1375
  roleName: currentRoles,
1378
- multiPortalUid
1376
+ ...portalContext.relation === "multiPortals" ? { multiPortalUid: portalContext.portalUid } : {}
1379
1377
  }
1380
1378
  });
1381
1379
  const routeIds = /* @__PURE__ */ new Set();
@@ -1391,7 +1389,7 @@ async function getMultiPortalAccessibleRouteIds(ctx, multiPortalUid) {
1391
1389
  const portalRoutes = await ctx.db.getRepository("desktopRoutes").find({
1392
1390
  fields: ["id"],
1393
1391
  filter: {
1394
- ...getDesktopRoutePortalFilter(multiPortalUid),
1392
+ ...portalContext.filter,
1395
1393
  id: Array.from(routeIds)
1396
1394
  }
1397
1395
  });
@@ -1421,9 +1419,107 @@ function setDesktopRouteChildren(route, children) {
1421
1419
  const maybeModel = route;
1422
1420
  if (typeof maybeModel.setDataValue === "function") {
1423
1421
  maybeModel.setDataValue("children", children);
1422
+ } else {
1423
+ maybeModel.children = children;
1424
+ }
1425
+ if (!maybeModel._options) {
1426
+ return;
1427
+ }
1428
+ if (!maybeModel._options.includeNames) {
1429
+ maybeModel._options.includeNames = ["children"];
1424
1430
  return;
1425
1431
  }
1426
- route.children = children;
1432
+ if (!maybeModel._options.includeNames.includes("children")) {
1433
+ maybeModel._options.includeNames.push("children");
1434
+ }
1435
+ }
1436
+ function getDesktopRouteId(route) {
1437
+ const id = getRecordField(route, "id");
1438
+ return id === null || id === void 0 ? void 0 : String(id);
1439
+ }
1440
+ function getDesktopRouteParentId(route) {
1441
+ const parentId = getRecordField(route, "parentId");
1442
+ return parentId === null || parentId === void 0 ? void 0 : String(parentId);
1443
+ }
1444
+ function collectDesktopRouteStringIds(routes, routeIds) {
1445
+ for (const route of routes) {
1446
+ const routeId = getDesktopRouteId(route);
1447
+ if (routeId) {
1448
+ routeIds.add(routeId);
1449
+ }
1450
+ const children = getRecordField(route, "children");
1451
+ if (Array.isArray(children)) {
1452
+ collectDesktopRouteStringIds(children, routeIds);
1453
+ }
1454
+ }
1455
+ }
1456
+ function removeNestedRootDesktopRoutes(routes) {
1457
+ if (!Array.isArray(routes)) {
1458
+ return [];
1459
+ }
1460
+ const routeIds = /* @__PURE__ */ new Set();
1461
+ collectDesktopRouteStringIds(routes, routeIds);
1462
+ return routes.filter((route) => {
1463
+ const parentId = getDesktopRouteParentId(route);
1464
+ return !parentId || !routeIds.has(parentId);
1465
+ });
1466
+ }
1467
+ function buildAccessibleDesktopRouteTreeWithAncestors(routes, accessibleRouteIds) {
1468
+ const routeById = /* @__PURE__ */ new Map();
1469
+ const childrenByParentId = /* @__PURE__ */ new Map();
1470
+ const roots = [];
1471
+ for (const route of routes) {
1472
+ const routeId = getDesktopRouteId(route);
1473
+ if (!routeId) {
1474
+ continue;
1475
+ }
1476
+ setDesktopRouteChildren(route, void 0);
1477
+ routeById.set(routeId, route);
1478
+ }
1479
+ for (const route of routes) {
1480
+ const routeId = getDesktopRouteId(route);
1481
+ if (!routeId || !routeById.has(routeId)) {
1482
+ continue;
1483
+ }
1484
+ const parentId = getDesktopRouteParentId(route);
1485
+ if (!parentId || !routeById.has(parentId)) {
1486
+ roots.push(route);
1487
+ continue;
1488
+ }
1489
+ const children = childrenByParentId.get(parentId) ?? [];
1490
+ children.push(route);
1491
+ childrenByParentId.set(parentId, children);
1492
+ }
1493
+ const visitRoute = (route, visitingRouteIds) => {
1494
+ const routeId = getDesktopRouteId(route);
1495
+ if (!routeId || visitingRouteIds.has(routeId)) {
1496
+ return void 0;
1497
+ }
1498
+ visitingRouteIds.add(routeId);
1499
+ const visibleChildren = (childrenByParentId.get(routeId) ?? []).map((child) => visitRoute(child, visitingRouteIds)).filter((child) => child !== void 0);
1500
+ visitingRouteIds.delete(routeId);
1501
+ if (!accessibleRouteIds.has(routeId) && visibleChildren.length === 0) {
1502
+ return void 0;
1503
+ }
1504
+ setDesktopRouteChildren(route, visibleChildren.length ? visibleChildren : void 0);
1505
+ return route;
1506
+ };
1507
+ return roots.map((route) => visitRoute(route, /* @__PURE__ */ new Set())).filter((route) => route !== void 0);
1508
+ }
1509
+ async function includeDesktopRouteAncestorsForListAccessible(ctx, routes, portalFilter) {
1510
+ if (!Array.isArray(routes)) {
1511
+ return routes;
1512
+ }
1513
+ const accessibleRouteIds = /* @__PURE__ */ new Set();
1514
+ collectDesktopRouteStringIds(routes, accessibleRouteIds);
1515
+ if (!accessibleRouteIds.size) {
1516
+ return routes;
1517
+ }
1518
+ const portalRoutes = await ctx.db.getRepository("desktopRoutes").find({
1519
+ sort: "sort",
1520
+ filter: portalFilter
1521
+ });
1522
+ return buildAccessibleDesktopRouteTreeWithAncestors(portalRoutes, accessibleRouteIds);
1427
1523
  }
1428
1524
  function removeDesktopRoutesByIds(routes, routeIds) {
1429
1525
  return routes.map((route) => {
@@ -1490,23 +1586,26 @@ async function removeMultiPortalOwnedRouteFromGetResponse(ctx) {
1490
1586
  ctx.body = void 0;
1491
1587
  }
1492
1588
  async function replaceListAccessibleRoutesWithPortalScopedRoutes(ctx, portalContext) {
1493
- const routeIds = await getMultiPortalAccessibleRouteIds(ctx, portalContext.portalUid);
1589
+ const routeIds = await getMultiPortalAccessibleRouteIds(ctx, portalContext);
1494
1590
  if (routeIds && routeIds.size === 0) {
1495
1591
  ctx.body = [];
1496
1592
  return;
1497
1593
  }
1498
- ctx.body = await ctx.db.getRepository("desktopRoutes").find({
1594
+ const routes = await ctx.db.getRepository("desktopRoutes").find({
1499
1595
  tree: true,
1500
1596
  sort: "sort",
1501
1597
  filter: {
1502
- ...getDesktopRoutePortalFilter(portalContext.portalUid),
1598
+ ...portalContext.filter,
1503
1599
  ...routeIds ? { id: Array.from(routeIds) } : {}
1504
1600
  }
1505
1601
  });
1602
+ ctx.body = removeNestedRootDesktopRoutes(
1603
+ await includeDesktopRouteAncestorsForListAccessible(ctx, routes, portalContext.filter)
1604
+ );
1506
1605
  }
1507
1606
  async function replaceGetAccessibleRouteWithPortalScopedRoute(ctx, portalContext) {
1508
1607
  var _a;
1509
- const routeIds = await getMultiPortalAccessibleRouteIds(ctx, portalContext.portalUid);
1608
+ const routeIds = await getMultiPortalAccessibleRouteIds(ctx, portalContext);
1510
1609
  if (routeIds && routeIds.size === 0) {
1511
1610
  ctx.status = 204;
1512
1611
  ctx.body = void 0;
@@ -1516,7 +1615,7 @@ async function replaceGetAccessibleRouteWithPortalScopedRoute(ctx, portalContext
1516
1615
  sort: "sort",
1517
1616
  filterByTk: (_a = ctx.action) == null ? void 0 : _a.params.filterByTk,
1518
1617
  filter: {
1519
- ...getDesktopRoutePortalFilter(portalContext.portalUid),
1618
+ ...portalContext.filter,
1520
1619
  ...routeIds ? { id: Array.from(routeIds) } : {}
1521
1620
  }
1522
1621
  });
@@ -1553,13 +1652,10 @@ async function addMultiPortalListAccessibleGuard(ctx, next) {
1553
1652
  return;
1554
1653
  }
1555
1654
  await next();
1556
- if ((portalContext == null ? void 0 : portalContext.relation) === "multiPortals") {
1655
+ if (portalContext) {
1557
1656
  await replaceListAccessibleRoutesWithPortalScopedRoutes(ctx, portalContext);
1558
1657
  return;
1559
1658
  }
1560
- if ((portalContext == null ? void 0 : portalContext.relation) === "uiLayouts") {
1561
- return;
1562
- }
1563
1659
  await removeMultiPortalOwnedRoutesFromListResponse(ctx);
1564
1660
  }
1565
1661
  async function addMultiPortalGetAccessibleGuard(ctx, next) {
@@ -1570,17 +1666,13 @@ async function addMultiPortalGetAccessibleGuard(ctx, next) {
1570
1666
  return;
1571
1667
  }
1572
1668
  await next();
1573
- if ((portalContext == null ? void 0 : portalContext.relation) === "multiPortals") {
1669
+ if (portalContext) {
1574
1670
  await replaceGetAccessibleRouteWithPortalScopedRoute(ctx, portalContext);
1575
1671
  return;
1576
1672
  }
1577
- if ((portalContext == null ? void 0 : portalContext.relation) === "uiLayouts") {
1578
- return;
1579
- }
1580
1673
  await removeMultiPortalOwnedRouteFromGetResponse(ctx);
1581
1674
  }
1582
1675
  async function mapMultiPortalLayoutToUiLayoutForRolePermissionTargets(ctx, next) {
1583
- var _a;
1584
1676
  const portalRequest = await findRequestedMultiPortal(ctx);
1585
1677
  if (portalRequest.requested && !portalRequest.portal) {
1586
1678
  ctx.status = 200;
@@ -1588,22 +1680,19 @@ async function mapMultiPortalLayoutToUiLayoutForRolePermissionTargets(ctx, next)
1588
1680
  return;
1589
1681
  }
1590
1682
  const scope = portalRequest.scope;
1591
- if ((scope == null ? void 0 : scope.relation) === "uiLayouts") {
1592
- if ((_a = ctx.action) == null ? void 0 : _a.params) {
1593
- ctx.action.params.layout = scope.uiLayoutUid;
1594
- }
1595
- await next();
1596
- return;
1597
- }
1598
- if ((scope == null ? void 0 : scope.relation) === "multiPortals") {
1683
+ if (scope) {
1599
1684
  const routes = await ctx.db.getRepository("desktopRoutes").find({
1600
- tree: true,
1601
1685
  sort: "sort",
1602
1686
  filter: scope.filter,
1603
1687
  fields: [...DESKTOP_ROUTE_ROLE_PERMISSION_TARGET_FIELDS]
1604
1688
  });
1689
+ const routeIds = /* @__PURE__ */ new Set();
1690
+ collectDesktopRouteStringIds(routes, routeIds);
1691
+ const routeTree = buildAccessibleDesktopRouteTreeWithAncestors(routes, routeIds);
1605
1692
  ctx.status = 200;
1606
- ctx.body = routes.map((route) => pickDesktopRouteRolePermissionTargetFields(route));
1693
+ ctx.body = removeNestedRootDesktopRoutes(routeTree).map(
1694
+ (route) => pickDesktopRouteRolePermissionTargetFields(route)
1695
+ );
1607
1696
  return;
1608
1697
  }
1609
1698
  await next();
@@ -2191,10 +2280,11 @@ async function listEnabledMultiPortals(ctx, next) {
2191
2280
  const records = await ctx.db.getRepository("multiPortals").find({
2192
2281
  filter: {
2193
2282
  enabled: true,
2194
- "uiLayout.enabled": true
2283
+ uiLayoutUid: {
2284
+ $in: [...import_constants.MULTI_PORTAL_UI_LAYOUT_UIDS]
2285
+ }
2195
2286
  },
2196
- fields: [...MULTI_PORTAL_RUNTIME_QUERY_FIELDS],
2197
- appends: ["uiLayout"],
2287
+ fields: [...MULTI_PORTAL_RUNTIME_FIELDS],
2198
2288
  sort: ["uid"]
2199
2289
  });
2200
2290
  ctx.body = records.map((record) => pickMultiPortalRuntimeFields(record));
@@ -2222,7 +2312,6 @@ async function findEnabledDefaultMultiPortal(ctx, transaction) {
2222
2312
  isDefault: true
2223
2313
  },
2224
2314
  fields: [...DEFAULT_MULTI_PORTAL_RESPONSE_FIELDS, "uiLayoutUid"],
2225
- appends: ["uiLayout"],
2226
2315
  transaction
2227
2316
  });
2228
2317
  if (!record) {
@@ -2232,11 +2321,8 @@ async function findEnabledDefaultMultiPortal(ctx, transaction) {
2232
2321
  if (!portalType) {
2233
2322
  return null;
2234
2323
  }
2235
- if (portalType === "no-code") {
2236
- const uiLayout = record.get("uiLayout");
2237
- if (!uiLayout || uiLayout.get("enabled") !== true) {
2238
- return null;
2239
- }
2324
+ if (!(0, import_constants.isMultiPortalUiLayoutUid)(record.get("uiLayoutUid"))) {
2325
+ return null;
2240
2326
  }
2241
2327
  return record;
2242
2328
  }
@@ -2274,21 +2360,9 @@ async function setDefaultMultiPortal(ctx, next) {
2274
2360
  ctx.throw(400, ctx.t("Unsupported Portal type cannot be set as default", { ns: import_constants.NAMESPACE }));
2275
2361
  return null;
2276
2362
  }
2277
- if (portalType === "no-code") {
2278
- const uiLayoutUid = target.get("uiLayoutUid");
2279
- const uiLayout = typeof uiLayoutUid === "string" && uiLayoutUid ? await ctx.db.getRepository("uiLayouts").findOne({
2280
- filter: {
2281
- uid: uiLayoutUid,
2282
- enabled: true
2283
- },
2284
- fields: ["uid"],
2285
- lock: transaction.LOCK.UPDATE,
2286
- transaction
2287
- }) : null;
2288
- if (!uiLayout) {
2289
- ctx.throw(400, ctx.t("Portal layout must be enabled before setting it as default", { ns: import_constants.NAMESPACE }));
2290
- return null;
2291
- }
2363
+ if (!(0, import_constants.isMultiPortalUiLayoutUid)(target.get("uiLayoutUid"))) {
2364
+ ctx.throw(400, ctx.t("Portal device configuration is invalid", { ns: import_constants.NAMESPACE }));
2365
+ return null;
2292
2366
  }
2293
2367
  await repository.update({
2294
2368
  filter: { isDefault: true },
@@ -2314,10 +2388,11 @@ async function listAccessibleMultiPortals(ctx, next) {
2314
2388
  const records = await ctx.db.getRepository("multiPortals").find({
2315
2389
  filter: {
2316
2390
  enabled: true,
2317
- "uiLayout.enabled": true
2391
+ uiLayoutUid: {
2392
+ $in: [...import_constants.MULTI_PORTAL_UI_LAYOUT_UIDS]
2393
+ }
2318
2394
  },
2319
- fields: [...MULTI_PORTAL_ACCESSIBLE_QUERY_FIELDS],
2320
- appends: ["uiLayout"],
2395
+ fields: [...MULTI_PORTAL_ACCESSIBLE_FIELDS],
2321
2396
  sort: ["uid"]
2322
2397
  });
2323
2398
  const accessiblePortalUidSet = Array.isArray(accessiblePortalUids) ? new Set(accessiblePortalUids) : void 0;
@@ -2461,6 +2536,9 @@ class PluginMultiPortalServer extends import_server.Plugin {
2461
2536
  getAppName() {
2462
2537
  return this.app.name || MAIN_APP_NAME;
2463
2538
  }
2539
+ getCurrentStorageAppName() {
2540
+ return normalizePortalStorageName(this.getAppName()) || MAIN_APP_NAME;
2541
+ }
2464
2542
  getMultiPortalStorageItem(multiPortal, previous = false) {
2465
2543
  const record = multiPortal;
2466
2544
  const readField = (field) => previous && typeof record.previous === "function" ? record.previous(field) : getRecordField(record, field);
@@ -2558,8 +2636,6 @@ class PluginMultiPortalServer extends import_server.Plugin {
2558
2636
  return;
2559
2637
  }
2560
2638
  this.logPortalBuildHtml(item, "skipped", "the portal is disabled");
2561
- await this.removePortalStorageIndexHtml(item);
2562
- await appendPortalStorageLog(logPath, `Portal index.html removed for ${item.appName}/${item.portalName}.`);
2563
2639
  } catch (error) {
2564
2640
  await appendPortalStorageLog(
2565
2641
  logPath,
@@ -2601,14 +2677,12 @@ class PluginMultiPortalServer extends import_server.Plugin {
2601
2677
  return;
2602
2678
  }
2603
2679
  this.logPortalBuildHtml(item, "skipped", "the portal is disabled");
2604
- await this.removePortalStorageIndexHtml(item);
2605
- await appendPortalStorageLog(logPath, `Portal index.html removed for ${item.appName}/${item.portalName}.`);
2606
2680
  }
2607
2681
  async syncMultiPortalStorageItem(multiPortal, options, syncPrevious = false, forceBuild = false) {
2608
2682
  const currentItem = this.getMultiPortalStorageItem(multiPortal);
2609
2683
  const previousItem = syncPrevious ? this.getMultiPortalStorageItem(multiPortal, true) : null;
2610
2684
  await this.runPortalStorageTask(async () => {
2611
- if (previousItem && (!currentItem || previousItem.appName !== currentItem.appName || previousItem.portalName !== currentItem.portalName || !currentItem.enabled)) {
2685
+ if (previousItem && (!currentItem || previousItem.appName !== currentItem.appName || previousItem.portalName !== currentItem.portalName)) {
2612
2686
  await this.removePortalStorageIndexHtml(previousItem);
2613
2687
  }
2614
2688
  if (currentItem) {
@@ -2655,12 +2729,12 @@ class PluginMultiPortalServer extends import_server.Plugin {
2655
2729
  await next();
2656
2730
  }
2657
2731
  async deployPortalDist(ctx, next) {
2658
- var _a, _b, _c, _d;
2732
+ var _a, _b, _c;
2659
2733
  const deployCtx = ctx;
2660
- const appName = normalizePortalStorageName(((_a = deployCtx.request.body) == null ? void 0 : _a.app) || MAIN_APP_NAME) || MAIN_APP_NAME;
2661
- const portalName = normalizePortalStorageName((_b = deployCtx.request.body) == null ? void 0 : _b.portal);
2662
- const basePath = trimString((_c = deployCtx.request.body) == null ? void 0 : _c.basePath);
2663
- const filePath = trimString((_d = deployCtx.request.file) == null ? void 0 : _d.path);
2734
+ const appName = this.getCurrentStorageAppName();
2735
+ const portalName = normalizePortalStorageName((_a = deployCtx.request.body) == null ? void 0 : _a.portal);
2736
+ const basePath = trimString((_b = deployCtx.request.body) == null ? void 0 : _b.basePath);
2737
+ const filePath = trimString((_c = deployCtx.request.file) == null ? void 0 : _c.path);
2664
2738
  try {
2665
2739
  if (!isValidPortalDeploySegment(appName)) {
2666
2740
  throw new Error("Invalid app");
@@ -2697,9 +2771,9 @@ class PluginMultiPortalServer extends import_server.Plugin {
2697
2771
  }
2698
2772
  }
2699
2773
  async pullPortalSource(ctx, next) {
2700
- var _a, _b;
2701
- const appName = normalizePortalStorageName(((_a = ctx.action.params.values) == null ? void 0 : _a.app) || MAIN_APP_NAME) || MAIN_APP_NAME;
2702
- const portalName = normalizePortalStorageName((_b = ctx.action.params.values) == null ? void 0 : _b.portal);
2774
+ var _a;
2775
+ const appName = this.getCurrentStorageAppName();
2776
+ const portalName = normalizePortalStorageName((_a = ctx.action.params.values) == null ? void 0 : _a.portal);
2703
2777
  try {
2704
2778
  if (!isValidPortalDeploySegment(appName)) {
2705
2779
  throw new Error("Invalid app");
@@ -2719,11 +2793,11 @@ class PluginMultiPortalServer extends import_server.Plugin {
2719
2793
  }
2720
2794
  }
2721
2795
  async pushPortalSource(ctx, next) {
2722
- var _a, _b, _c;
2796
+ var _a, _b;
2723
2797
  const sourceCtx = ctx;
2724
- const appName = normalizePortalStorageName(((_a = sourceCtx.request.body) == null ? void 0 : _a.app) || MAIN_APP_NAME) || MAIN_APP_NAME;
2725
- const portalName = normalizePortalStorageName((_b = sourceCtx.request.body) == null ? void 0 : _b.portal);
2726
- const filePath = trimString((_c = sourceCtx.request.file) == null ? void 0 : _c.path);
2798
+ const appName = this.getCurrentStorageAppName();
2799
+ const portalName = normalizePortalStorageName((_a = sourceCtx.request.body) == null ? void 0 : _a.portal);
2800
+ const filePath = trimString((_b = sourceCtx.request.file) == null ? void 0 : _b.path);
2727
2801
  try {
2728
2802
  if (!isValidPortalDeploySegment(appName)) {
2729
2803
  throw new Error("Invalid app");
@@ -2777,38 +2851,20 @@ class PluginMultiPortalServer extends import_server.Plugin {
2777
2851
  const records = await this.db.getRepository("multiPortals").find({
2778
2852
  filter: {
2779
2853
  enabled: true,
2780
- "uiLayout.enabled": true
2854
+ uiLayoutUid: {
2855
+ $in: [...import_constants.MULTI_PORTAL_UI_LAYOUT_UIDS]
2856
+ }
2781
2857
  },
2782
- fields: [...MULTI_PORTAL_ACCESSIBLE_QUERY_FIELDS],
2783
- appends: ["uiLayout"],
2858
+ fields: [...MULTI_PORTAL_ACCESSIBLE_FIELDS],
2784
2859
  sort: ["uid"],
2785
2860
  transaction: options == null ? void 0 : options.transaction
2786
2861
  });
2787
- return records.map((record) => {
2788
- const portal = pickMultiPortalAccessibleFields(record);
2789
- const uiLayout = portal.uiLayout;
2790
- const uid = typeof portal.uid === "string" ? portal.uid : "";
2791
- const title = typeof portal.title === "string" ? portal.title : "";
2792
- return {
2793
- uid,
2794
- title,
2795
- icon: typeof portal.icon === "string" ? portal.icon : null,
2796
- portalType: typeof portal.portalType === "string" ? portal.portalType : null,
2797
- routePath: String(portal.routePath || ""),
2798
- layout: typeof (uiLayout == null ? void 0 : uiLayout.layoutType) === "string" ? uiLayout.layoutType : null
2799
- };
2862
+ return records.flatMap((record) => {
2863
+ const item = this.toAppPortalManifestItem(record);
2864
+ return item ? [item] : [];
2800
2865
  });
2801
2866
  }
2802
- async getUiLayout(uid, options) {
2803
- if (typeof uid !== "string" || !uid) {
2804
- return null;
2805
- }
2806
- return this.db.getRepository("uiLayouts").findOne({
2807
- filterByTk: uid,
2808
- transaction: options == null ? void 0 : options.transaction
2809
- });
2810
- }
2811
- async toAppPortalManifestItem(multiPortal, options) {
2867
+ toAppPortalManifestItem(multiPortal) {
2812
2868
  const uid = getRecordField(multiPortal, "uid");
2813
2869
  const title = getRecordField(multiPortal, "title");
2814
2870
  const portalType = getRecordField(multiPortal, "portalType");
@@ -2818,19 +2874,18 @@ class PluginMultiPortalServer extends import_server.Plugin {
2818
2874
  return null;
2819
2875
  }
2820
2876
  const uiLayoutUid = getRecordField(multiPortal, "uiLayoutUid");
2821
- const uiLayout = getRecordField(multiPortal, "uiLayout") || await this.getUiLayout(uiLayoutUid, options);
2822
- if (!uiLayout || getRecordField(uiLayout, "enabled") !== true) {
2877
+ const layout = (0, import_constants.getMultiPortalLayoutType)(uiLayoutUid);
2878
+ if (!layout) {
2823
2879
  return null;
2824
2880
  }
2825
2881
  const icon = getRecordField(multiPortal, "icon");
2826
- const layoutType = getRecordField(uiLayout, "layoutType");
2827
2882
  return {
2828
2883
  uid,
2829
2884
  title,
2830
2885
  icon: typeof icon === "string" ? icon : null,
2831
2886
  portalType: typeof portalType === "string" ? portalType : null,
2832
2887
  routePath,
2833
- layout: typeof layoutType === "string" ? layoutType : null
2888
+ layout
2834
2889
  };
2835
2890
  }
2836
2891
  async setAppManifestItem(item, options) {
@@ -2873,30 +2928,13 @@ class PluginMultiPortalServer extends import_server.Plugin {
2873
2928
  if (typeof uid !== "string" || !uid) {
2874
2929
  return;
2875
2930
  }
2876
- const item = await this.toAppPortalManifestItem(multiPortal, options);
2931
+ const item = this.toAppPortalManifestItem(multiPortal);
2877
2932
  if (item) {
2878
2933
  await this.setAppManifestItem(item, options);
2879
2934
  return;
2880
2935
  }
2881
2936
  await this.removeAppManifestItem(uid, options);
2882
2937
  }
2883
- async publishUiLayoutManifestItems(uiLayout, options) {
2884
- const uiLayoutUid = getRecordField(uiLayout, "uid");
2885
- if (typeof uiLayoutUid !== "string" || !uiLayoutUid) {
2886
- return;
2887
- }
2888
- const records = await this.db.getRepository("multiPortals").find({
2889
- filter: {
2890
- uiLayoutUid
2891
- },
2892
- fields: [...MULTI_PORTAL_ACCESSIBLE_QUERY_FIELDS],
2893
- transaction: options == null ? void 0 : options.transaction
2894
- });
2895
- for (const record of records) {
2896
- record.set("uiLayout", uiLayout);
2897
- await this.publishAppManifestItem(record, options);
2898
- }
2899
- }
2900
2938
  async reconcilePortalStorage(options) {
2901
2939
  const records = await this.db.getRepository("multiPortals").find({
2902
2940
  filter: {
@@ -2960,15 +2998,16 @@ class PluginMultiPortalServer extends import_server.Plugin {
2960
2998
  mapMultiPortalLayoutToUiLayoutForRolePermissionTargets
2961
2999
  );
2962
3000
  this.app.resourceManager.registerPreActionHandler("multiPortals:create", preventDirectDefaultPortalMutation);
3001
+ this.app.resourceManager.registerPreActionHandler("multiPortals:create", validateMultiPortalUiLayoutUidWrite);
2963
3002
  this.app.resourceManager.registerPreActionHandler("multiPortals:create", captureSkipCreatePortalDirectory);
2964
3003
  this.app.resourceManager.registerPreActionHandler("multiPortals:create", normalizeMultiPortalSlugValues);
2965
3004
  this.app.resourceManager.registerPreActionHandler("multiPortals:update", preventDirectDefaultPortalMutation);
2966
- this.app.resourceManager.registerPreActionHandler("multiPortals:update", preventMultiPortalBackingLayoutChange);
3005
+ this.app.resourceManager.registerPreActionHandler("multiPortals:update", validateMultiPortalUiLayoutUidWrite);
2967
3006
  this.app.resourceManager.registerPreActionHandler("multiPortals:update", normalizeMultiPortalSlugValues);
2968
3007
  this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", preventDirectDefaultPortalMutation);
2969
3008
  this.app.resourceManager.registerPreActionHandler(
2970
3009
  "multiPortals:firstOrCreate",
2971
- preventMultiPortalBackingLayoutChange
3010
+ validateMultiPortalUiLayoutUidWrite
2972
3011
  );
2973
3012
  this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", captureSkipCreatePortalDirectory);
2974
3013
  this.app.resourceManager.registerPreActionHandler("multiPortals:firstOrCreate", normalizeMultiPortalSlugValues);
@@ -2978,7 +3017,7 @@ class PluginMultiPortalServer extends import_server.Plugin {
2978
3017
  );
2979
3018
  this.app.resourceManager.registerPreActionHandler(
2980
3019
  "multiPortals:updateOrCreate",
2981
- preventMultiPortalBackingLayoutChange
3020
+ validateMultiPortalUiLayoutUidWrite
2982
3021
  );
2983
3022
  this.app.resourceManager.registerPreActionHandler("multiPortals:updateOrCreate", captureSkipCreatePortalDirectory);
2984
3023
  this.app.resourceManager.registerPreActionHandler("multiPortals:updateOrCreate", normalizeMultiPortalSlugValues);
@@ -3063,9 +3102,6 @@ class PluginMultiPortalServer extends import_server.Plugin {
3063
3102
  }
3064
3103
  await this.removeMultiPortalStorageItem(multiPortal, options);
3065
3104
  });
3066
- this.app.db.on("uiLayouts.afterUpdate", async (uiLayout, options) => {
3067
- await this.publishUiLayoutManifestItems(uiLayout, options);
3068
- });
3069
3105
  }
3070
3106
  async install() {
3071
3107
  const version = await this.app.version.get();