Add BIM viewer service launch integration

This commit is contained in:
DCCONSTRUCTIONS 2026-06-18 21:43:04 +03:00
parent ec021d1a95
commit 4584c227ff
3 changed files with 193 additions and 1 deletions

View File

@ -62,6 +62,48 @@ const publicPoolClient = {
createdAt: "2026-05-09T00:00:00.000Z",
updatedAt: "2026-05-09T00:00:00.000Z",
};
const bimViewerService = {
id: "service_bim_viewer",
slug: "bim-viewer",
title: "BIM VIEWER",
subtitle: "BIM и CAD просмотр",
description: "Персональный просмотр BIM/CAD моделей, версий и публичных ссылок.",
fullDescription:
"Standalone BIM/CAD viewer NODE.DC для рабочих моделей, вложений Operational Core, персонального модельного хранилища и безопасного внешнего просмотра по share-ссылкам.",
url: "https://bim.nodedc.ru",
launchUrl: "https://bim.nodedc.ru",
accentColor: "#8FE3C8",
fallbackGradient: "linear-gradient(132deg, rgba(143, 227, 200, 0.78), rgba(24, 70, 64, 0.9) 46%, #080B0F 84%)",
status: "active",
order: 25,
authentikApplicationSlug: "bim-viewer",
authentikGroupName: "nodedc:bim:access",
isAvailableForAllNewClients: true,
createdAt: "2026-06-18T00:00:00.000Z",
updatedAt: "2026-06-18T00:00:00.000Z",
};
const bimViewerDefaultGrants = [
{
id: "grant_bim_viewer_client_romashka",
serviceId: "service_bim_viewer",
targetType: "client",
targetId: "client_romashka",
appRole: "member",
status: "active",
createdAt: "2026-06-18T00:00:00.000Z",
updatedAt: "2026-06-18T00:00:00.000Z",
},
{
id: "grant_bim_viewer_client_public_pool",
serviceId: "service_bim_viewer",
targetType: "client",
targetId: publicPoolClientId,
appRole: "member",
status: "active",
createdAt: "2026-06-18T00:00:00.000Z",
updatedAt: "2026-06-18T00:00:00.000Z",
},
];
const defaultSettings = {
brand: {
logoLinkUrl: "/",
@ -2281,9 +2323,89 @@ function normalizeData(payload) {
data.taskerInviteRequests = data.taskerInviteRequests.map(normalizeTaskerInviteRequest).filter(Boolean);
data.engineWorkflowAccessRequests = data.engineWorkflowAccessRequests.map(normalizeEngineWorkflowAccessRequest).filter(Boolean);
applyProtectedLauncherUserInvariants(data);
applyBimViewerServiceInvariants(data);
return data;
}
function applyBimViewerServiceInvariants(data) {
let service = data.services.find(
(candidate) => candidate.id === bimViewerService.id || candidate.slug === bimViewerService.slug
);
if (!service) {
service = normalizeService({ ...bimViewerService });
data.services.push(service);
} else {
service.id = service.id || bimViewerService.id;
service.slug = service.slug || bimViewerService.slug;
service.title = service.title || bimViewerService.title;
service.subtitle = service.subtitle || bimViewerService.subtitle;
service.description = service.description || bimViewerService.description;
service.fullDescription = service.fullDescription || bimViewerService.fullDescription;
service.url = service.url || bimViewerService.url;
service.launchUrl = service.launchUrl || service.url || bimViewerService.launchUrl;
service.accentColor = service.accentColor || bimViewerService.accentColor;
service.fallbackGradient = service.fallbackGradient || bimViewerService.fallbackGradient;
service.status = service.status || bimViewerService.status;
service.order = service.order ?? bimViewerService.order;
service.authentikApplicationSlug = service.authentikApplicationSlug || bimViewerService.authentikApplicationSlug;
service.authentikGroupName = service.authentikGroupName || bimViewerService.authentikGroupName;
service.isAvailableForAllNewClients = service.isAvailableForAllNewClients ?? true;
service.createdAt = service.createdAt || bimViewerService.createdAt;
service.updatedAt = service.updatedAt || bimViewerService.updatedAt;
}
const serviceId = service.id || bimViewerService.id;
for (const defaultGrant of bimViewerDefaultGrants) {
const grant = { ...defaultGrant, serviceId };
const exists = data.grants.some(
(candidate) =>
candidate.serviceId === grant.serviceId &&
candidate.targetType === grant.targetType &&
candidate.targetId === grant.targetId
);
if (!exists) {
data.grants.push({ ...grant });
}
}
ensureStaticSyncStatus(data, {
id: "sync_bim_viewer_authentik",
objectId: serviceId,
objectName: bimViewerService.title,
objectType: "service",
});
for (const defaultGrant of bimViewerDefaultGrants) {
const grant = { ...defaultGrant, serviceId };
ensureStaticSyncStatus(data, {
id: `sync_${grant.id}`,
objectId: grant.id,
objectName: `${bimViewerService.slug}:${grant.targetType}:${grant.targetId}`,
objectType: "grant",
});
}
}
function ensureStaticSyncStatus(data, status) {
const exists = data.syncStatuses.some(
(candidate) => candidate.target === "authentik" && candidate.objectType === status.objectType && candidate.objectId === status.objectId
);
if (exists) {
return;
}
data.syncStatuses.push({
id: status.id,
objectId: status.objectId,
objectName: status.objectName,
objectType: status.objectType,
target: "authentik",
state: "pending",
lastSyncAt: null,
error: null,
updatedAt: "2026-06-18T00:00:00.000Z",
});
}
function applyProtectedLauncherUserInvariants(data) {
const rootUser = data.users.find((user) => isProtectedLauncherUserId(user.id));
if (!rootUser) return;

View File

@ -422,6 +422,21 @@ app.get("/api/services/:serviceSlug/launch", requireSession, (req, res) => {
return;
}
if (isBimViewerServiceSlug(serviceSlug)) {
const handoffToken = createServiceHandoff(serviceSlug, runtimeContext.user);
const targetUrl = new URL("/auth/nodedc/handoff", getBimViewerBaseUrl(app));
const nextPath = sanitizeReturnTo(req.query.next_path || req.query.returnTo || "/");
targetUrl.searchParams.set("token", handoffToken);
if (nextPath && nextPath !== "/") {
targetUrl.searchParams.set("next_path", nextPath);
}
res.redirect(targetUrl.toString());
return;
}
if (serviceSlug !== "task-manager") {
res.redirect(app.openUrl || app.url || "/");
return;
@ -3031,6 +3046,11 @@ function isEngineServiceSlug(serviceSlug) {
return slug === "nodedc" || slug === "engine" || slug === "nodedc-engine";
}
function isBimViewerServiceSlug(serviceSlug) {
const slug = sanitizeServiceSlug(serviceSlug);
return slug === "bim-viewer" || slug === "beam-viewer" || slug === "bim";
}
function specialRequiredGroups(serviceOrSlug) {
const slug = typeof serviceOrSlug === "string" ? serviceOrSlug : serviceOrSlug?.slug;
const applicationSlug = typeof serviceOrSlug === "string" ? null : serviceOrSlug?.authentikApplicationSlug;
@ -3046,7 +3066,7 @@ function specialRequiredGroups(serviceOrSlug) {
}
function getServiceUrl(service) {
if (service.slug === "task-manager" || isEngineServiceSlug(service.slug)) {
if (service.slug === "task-manager" || isEngineServiceSlug(service.slug) || isBimViewerServiceSlug(service.slug)) {
return `/api/services/${encodeURIComponent(service.slug)}/launch`;
}
@ -3066,6 +3086,17 @@ function getTaskBaseUrl() {
return taskBaseUrl.replace(/\/$/, "");
}
function getBimViewerBaseUrl(app = null) {
return String(
process.env.NODEDC_BIM_VIEWER_PUBLIC_URL ||
process.env.BIM_VIEWER_BASE_URL ||
app?.launchTargetUrl ||
app?.openUrl ||
app?.url ||
"http://localhost:8080"
).replace(/\/$/, "");
}
function getEnginePublicBaseUrl() {
return String(
process.env.NODEDC_ENGINE_PUBLIC_URL ||

View File

@ -113,6 +113,26 @@ export const mockServices: Service[] = [
createdAt: "2026-04-01T10:00:00Z",
updatedAt: now,
},
{
id: "service_bim_viewer",
slug: "bim-viewer",
title: "BIM VIEWER",
subtitle: "BIM и CAD просмотр",
description: "Персональный просмотр BIM/CAD моделей, версий и публичных ссылок.",
fullDescription:
"Standalone BIM/CAD viewer NODE.DC для рабочих моделей, вложений Operational Core, персонального модельного хранилища и безопасного внешнего просмотра по share-ссылкам.",
url: "https://bim.nodedc.ru",
launchUrl: "https://bim.nodedc.ru",
accentColor: "#8FE3C8",
fallbackGradient: "linear-gradient(132deg, rgba(143, 227, 200, 0.78), rgba(24, 70, 64, 0.9) 46%, #080B0F 84%)",
status: "active",
order: 25,
authentikApplicationSlug: "bim-viewer",
authentikGroupName: "nodedc:bim:access",
isAvailableForAllNewClients: true,
createdAt: "2026-06-18T00:00:00.000Z",
updatedAt: now,
},
{
id: "service_1c",
slug: "1c-assistant",
@ -211,6 +231,8 @@ export const mockGrants: ServiceGrant[] = [
grant("grant_dctouch_nodedc_admins", "service_nodedc", "group", "group_dctouch_admins", "admin"),
grant("grant_engine_user_silver_psih_yahoo_com", "service_nodedc", "user", "user_silver_psih", "member"),
grant("grant_engine_user_constr_dc_yahoo_com", "service_nodedc", "user", "user_constr_dc_yahoo_com", "viewer"),
grant("grant_bim_viewer_client_romashka", "service_bim_viewer", "client", "client_romashka", "member"),
grant("grant_bim_viewer_client_public_pool", "service_bim_viewer", "client", "client_public_pool", "member"),
];
export const mockExceptions: ServiceAccessException[] = [];
@ -235,6 +257,23 @@ export const mockSyncStatuses: SyncStatus[] = [
),
sync("sync_dctouch_groups_authentik", "client_romashka:groups", "DCTOUCH groups", "group", "authentik", "pending"),
sync("sync_task_manager_authentik", "service_task_manager", "OPERATIONAL CORE", "service", "authentik", "synced"),
sync("sync_bim_viewer_authentik", "service_bim_viewer", "BIM VIEWER", "service", "authentik", "pending"),
sync(
"sync_grant_bim_viewer_client_romashka",
"grant_bim_viewer_client_romashka",
"bim-viewer:client:client_romashka",
"grant",
"authentik",
"pending"
),
sync(
"sync_grant_bim_viewer_client_public_pool",
"grant_bim_viewer_client_public_pool",
"bim-viewer:client:client_public_pool",
"grant",
"authentik",
"pending"
),
];
export const mockAuditEvents: AuditEvent[] = [