Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,169 @@
|
||||
import { Params, redirect } from "react-router";
|
||||
import { DefaultAdminRoles } from "~/application/dtos/shared/DefaultAdminRoles";
|
||||
import { DefaultAppRoles } from "~/application/dtos/shared/DefaultAppRoles";
|
||||
import { DefaultEntityTypes } from "~/application/dtos/shared/DefaultEntityTypes";
|
||||
import { DefaultPermission } from "~/application/dtos/shared/DefaultPermissions";
|
||||
import { TenantUserType } from "~/application/enums/tenants/TenantUserType";
|
||||
import { timeFake, TimeFunction } from "~/modules/metrics/services/.server/MetricTracker";
|
||||
import OnboardingService from "~/modules/onboarding/services/OnboardingService";
|
||||
import { absoluteAppRootPath } from "~/utils/app/CrossHostUrls";
|
||||
import { appPath } from "~/utils/app/CanonicalUrlUtils";
|
||||
import UrlUtils from "~/utils/app/UrlUtils";
|
||||
import { loadRequiredWebConfig } from "@toolctrl/config";
|
||||
import { getAllEntities } from "~/utils/db/entities/entities.db.server";
|
||||
import { getAllEntityGroups } from "~/utils/db/entities/entityGroups.db.server";
|
||||
import { getMyGroups } from "~/utils/db/permissions/groups.db.server";
|
||||
import { getAllRolesWithoutPermissions } from "~/utils/db/permissions/roles.db.server";
|
||||
import { getPermissionsByUser, getUserRoleInTenant, getUserRoleInAdmin } from "~/utils/db/permissions/userRoles.db.server";
|
||||
import { adminGetAllActiveTenantSimple, getTenantSimple, getTenantUserType } from "~/utils/db/tenants.db.server";
|
||||
import { getAuthUserWithoutPasswordById as getUser, getTenantsForUser as getMyTenants } from "@toolctrl/db/auth-boundary.server";
|
||||
import { getAllTenantTypes } from "~/utils/db/tenants/tenantTypes.db.server";
|
||||
import { promiseHash } from "~/utils/promises/promiseHash";
|
||||
import { getTenantIdFromUrl } from "~/utils/services/.server/urlService";
|
||||
import { getActiveTenantSubscriptions, getPlanFeatureUsage } from "~/utils/services/.server/subscriptionService";
|
||||
import { getUserInfo } from "~/utils/session.server";
|
||||
import { AppLoaderData } from "../useAppData";
|
||||
import { PlanFeatureUsageDto } from "~/application/dtos/subscriptions/PlanFeatureUsageDto";
|
||||
import { CreditTypes } from "~/modules/usage/dtos/CreditType";
|
||||
import { DefaultFeatures } from "~/application/dtos/shared/DefaultFeatures";
|
||||
import { TFunction } from "i18next";
|
||||
import { featureKeys, type FeatureKey } from "@toolctrl/entitlements";
|
||||
import { resolveTenantProductAccess } from "~/utils/entitlements/.server/productAccess.server";
|
||||
import { WALKING_SKELETON_MODULE_KEY } from "~/modules/walkingSkeleton/constants";
|
||||
|
||||
const tenantNavigationFeatureKeys = [
|
||||
featureKeys.crmBasic,
|
||||
featureKeys.emailMarketing,
|
||||
featureKeys.api,
|
||||
featureKeys.workflowEngine,
|
||||
featureKeys.credits,
|
||||
] as const satisfies readonly FeatureKey[];
|
||||
|
||||
// R-P1c: Konnektor-Demo-Modul (kein Entitlement-FeatureKey) — speist das Sidebar-Gate der
|
||||
// Walking-Skeleton-Route. Liefert für reale Tenants "blueprint-disabled" → Eintrag ausgeblendet.
|
||||
const tenantConnectorDemoModuleKeys: readonly string[] = [WALKING_SKELETON_MODULE_KEY];
|
||||
|
||||
export async function loadAppData({
|
||||
request,
|
||||
params,
|
||||
t,
|
||||
time = timeFake,
|
||||
tenantId: preloadedTenantId,
|
||||
userInfo: preloadedUserInfo,
|
||||
user: preloadedUser,
|
||||
}: {
|
||||
request: Request;
|
||||
params: Params;
|
||||
t: TFunction;
|
||||
time?: TimeFunction;
|
||||
tenantId?: string;
|
||||
userInfo?: Awaited<ReturnType<typeof getUserInfo>>;
|
||||
user?: Awaited<ReturnType<typeof getUser>>;
|
||||
}) {
|
||||
const { tenantId, userInfo } = await time(
|
||||
promiseHash({
|
||||
tenantId: preloadedTenantId ? Promise.resolve(preloadedTenantId) : getTenantIdFromUrl(params),
|
||||
userInfo: preloadedUserInfo ? Promise.resolve(preloadedUserInfo) : getUserInfo(request),
|
||||
}),
|
||||
"loadAppData.session"
|
||||
);
|
||||
|
||||
const url = new URL(request.url);
|
||||
const { user, currentTenant } = await time(
|
||||
promiseHash({
|
||||
user: preloadedUser ? Promise.resolve(preloadedUser) : getUser(userInfo?.userId),
|
||||
currentTenant: getTenantSimple(tenantId),
|
||||
}),
|
||||
"loadAppData.getUserAndTenant"
|
||||
);
|
||||
|
||||
const redirectTo = url.pathname + url.search;
|
||||
if (!userInfo || !user) {
|
||||
let searchParams = new URLSearchParams([["redirect", redirectTo]]);
|
||||
throw redirect(`/login?${searchParams}`);
|
||||
}
|
||||
|
||||
if (!currentTenant) {
|
||||
throw redirect(absoluteAppRootPath(loadRequiredWebConfig().routing));
|
||||
}
|
||||
const [, tenantPathSegment = "", ...childPathSegments] = url.pathname.split("/");
|
||||
const childPath = childPathSegments.join("/");
|
||||
if (params.tenant && params.tenant !== currentTenant.slug) {
|
||||
throw redirect(`${appPath({ tenantSlug: currentTenant.slug, path: childPath || "dashboard", mode: "host-aware" })}${url.search}`);
|
||||
}
|
||||
if (UrlUtils.stripTrailingSlash(url.pathname) === UrlUtils.stripTrailingSlash(appPath({ tenantSlug: tenantPathSegment, mode: "host-aware" }))) {
|
||||
throw redirect(`${appPath({ tenantSlug: currentTenant.slug, path: "dashboard", mode: "host-aware" })}${url.search}`);
|
||||
}
|
||||
let { myTenants, mySubscription, allPermissions, superUserRole, superAdminRole, entities, entityGroups, allRoles, myGroups, onboardingSession } = await time(
|
||||
promiseHash({
|
||||
myTenants: time(user.admin ? adminGetAllActiveTenantSimple() : getMyTenants(user.id), "loadAppData.getDetails.getMyTenants"),
|
||||
mySubscription: time(getActiveTenantSubscriptions(tenantId), "loadAppData.getDetails.getActiveTenantSubscriptions"),
|
||||
allPermissions: time(getPermissionsByUser(userInfo.userId, tenantId), "loadAppData.getDetails.getPermissionsByUser"),
|
||||
superUserRole: time(getUserRoleInTenant(userInfo.userId, tenantId, DefaultAppRoles.SuperUser), "loadAppData.getDetails.getUserRoleInTenant"),
|
||||
superAdminRole: time(getUserRoleInAdmin(userInfo.userId, DefaultAdminRoles.SuperAdmin), "loadAppData.getDetails.getUserRoleInAdmin"),
|
||||
entities: time(
|
||||
getAllEntities({ tenantId, active: true, types: [DefaultEntityTypes.All, DefaultEntityTypes.AppOnly] }),
|
||||
"loadAppData.getDetails.getAllEntities"
|
||||
),
|
||||
entityGroups: time(getAllEntityGroups(), "loadAppData.getDetails.getAllEntityGroups()"),
|
||||
allRoles: time(getAllRolesWithoutPermissions("app"), "loadAppData.getDetails.getAllRolesWithoutPermissions"),
|
||||
myGroups: time(getMyGroups(user.id, currentTenant.id), "loadAppData.getDetails.getMyGroups"),
|
||||
onboardingSession: time(
|
||||
OnboardingService.getUserActiveOnboarding({ userId: user.id, tenantId: currentTenant.id, request }),
|
||||
"loadAppData.getDetails.OnboardingService.getUserActiveOnboarding"
|
||||
),
|
||||
}),
|
||||
"loadAppData.getDetails"
|
||||
);
|
||||
|
||||
const { tenantUser, tenantTypes, credits, productAccessEntries } = await time(
|
||||
promiseHash({
|
||||
tenantUser: time(getTenantUserType(userInfo.userId, tenantId), "loadAppData.postDetails.getTenantUserType"),
|
||||
tenantTypes: time(getAllTenantTypes(), "loadAppData.postDetails.getAllTenantTypes"),
|
||||
credits:
|
||||
CreditTypes.length > 0
|
||||
? time(getPlanFeatureUsage(tenantId, DefaultFeatures.Credits), "loadAppData.postDetails.getPlanFeatureUsage")
|
||||
: Promise.resolve(undefined as PlanFeatureUsageDto | undefined),
|
||||
productAccessEntries: time(
|
||||
Promise.all(
|
||||
[...tenantNavigationFeatureKeys, ...tenantConnectorDemoModuleKeys].map(async (featureKey) => [
|
||||
featureKey,
|
||||
await resolveTenantProductAccess({
|
||||
tenantId,
|
||||
userId: userInfo.userId,
|
||||
featureKey,
|
||||
subscription: mySubscription,
|
||||
tenantRolePermissionNames: allPermissions,
|
||||
bypassTenantRolePermission: !!user.admin || !!superAdminRole,
|
||||
}),
|
||||
] as const)
|
||||
),
|
||||
"loadAppData.postDetails.resolveTenantProductAccess"
|
||||
),
|
||||
}),
|
||||
"loadAppData.postDetails"
|
||||
);
|
||||
const currentRole = user.admin ? TenantUserType.ADMIN : tenantUser?.type ?? TenantUserType.MEMBER;
|
||||
const data: AppLoaderData = {
|
||||
// i18n: i18n.translations,
|
||||
user,
|
||||
myTenants,
|
||||
currentTenant,
|
||||
currentRole,
|
||||
mySubscription,
|
||||
entities,
|
||||
entityGroups,
|
||||
// roles,
|
||||
allRoles,
|
||||
permissions: allPermissions.map((f) => f as DefaultPermission),
|
||||
myGroups,
|
||||
isSuperUser: !!superUserRole,
|
||||
isSuperAdmin: !!superAdminRole,
|
||||
lng: user?.locale ?? userInfo.lng,
|
||||
onboardingSession,
|
||||
tenantTypes,
|
||||
credits,
|
||||
productAccess: Object.fromEntries(productAccessEntries),
|
||||
};
|
||||
return data;
|
||||
}
|
||||
Reference in new issue
Block a user