import { existsSync, readFileSync } from "node:fs"; import { mkdir, writeFile } from "node:fs/promises"; import { dirname, isAbsolute, join, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const projectRoot = dirname(dirname(fileURLToPath(import.meta.url))); const serverStorageRoot = resolveStorageRoot(projectRoot); const dataPath = join(serverStorageRoot, "launcher-data.json"); const legacyPublicDataPath = join(projectRoot, "public", "storage", "launcher-data.json"); const now = new Date().toISOString(); const existingData = existsSync(dataPath) ? readJson(dataPath) : readJson(legacyPublicDataPath); const services = Array.isArray(existingData.services) ? existingData.services : []; const existingUsersByEmail = new Map( (Array.isArray(existingData.users) ? existingData.users : []).map((user) => [String(user.email || "").toLowerCase(), user]) ); const dcTouchAuthentikUserId = existingUsersByEmail.get("dcctouch@gmail.com")?.authentikUserId ?? null; const silverPsihAuthentikUserId = existingUsersByEmail.get("silver_psih@yahoo.com")?.authentikUserId ?? null; const liveData = { ...existingData, clients: [ { id: "client_romashka", type: "company", name: "DCTOUCH", legalName: "ООО ДИСИТАЧ", status: "active", contractStartsAt: "2026-05-04T00:00:00.000Z", contractEndsAt: null, paidUntil: null, demoEndsAt: null, contactName: "DC Touch", contactEmail: "dcctouch@gmail.com", notes: "Live-клиент NODE.DC для первичной проверки control-plane, SSO и доступа к сервисам.", createdAt: "2026-05-04T00:00:00.000Z", updatedAt: now, }, ], users: [ { id: "user_root", authentikUserId: dcTouchAuthentikUserId, name: "DC Touch", email: "dcctouch@gmail.com", phone: null, position: "NODE.DC Super Admin", notes: "Главный супер-администратор NODE.DC. Authentik-пользователь уже создан в dev-контуре.", avatarUrl: null, globalStatus: "active", createdAt: "2026-05-04T00:00:00.000Z", updatedAt: now, }, { id: "user_silver_psih", authentikUserId: silverPsihAuthentikUserId, name: "Silver Psy", email: "silver_psih@yahoo.com", phone: null, position: "Manager", notes: "Живой пользователь из Plane. Требует создания/синхронизации в Authentik через Launcher flow.", avatarUrl: null, globalStatus: "active", createdAt: "2026-05-04T00:00:00.000Z", updatedAt: now, }, ], memberships: [ { id: "mem_dc_touch_dctouch", clientId: "client_romashka", userId: "user_root", role: "client_owner", status: "active", createdAt: "2026-05-04T00:00:00.000Z", updatedAt: now, }, { id: "mem_silver_psih_dctouch", clientId: "client_romashka", userId: "user_silver_psih", role: "member", status: "active", createdAt: "2026-05-04T00:00:00.000Z", updatedAt: now, }, ], groups: [ { id: "group_dctouch_admins", clientId: "client_romashka", name: "Администраторы", description: "Администраторы клиента и владельцы платформенного доступа.", memberIds: ["user_root"], createdAt: "2026-05-04T00:00:00.000Z", updatedAt: now, }, { id: "group_dctouch_managers", clientId: "client_romashka", name: "Менеджеры", description: "Рабочая группа менеджеров с доступом к операционному контуру.", memberIds: ["user_silver_psih"], createdAt: "2026-05-04T00:00:00.000Z", updatedAt: now, }, ], grants: [ { id: "grant_dctouch_task_admins", serviceId: "service_task_manager", targetType: "group", targetId: "group_dctouch_admins", appRole: "admin", status: "active", createdAt: "2026-05-04T00:00:00.000Z", updatedAt: now, }, { id: "grant_dctouch_task_managers", serviceId: "service_task_manager", targetType: "group", targetId: "group_dctouch_managers", appRole: "member", status: "active", createdAt: "2026-05-04T00:00:00.000Z", updatedAt: now, }, { id: "grant_dctouch_nodedc_admins", serviceId: "service_nodedc", targetType: "group", targetId: "group_dctouch_admins", appRole: "admin", status: "active", createdAt: "2026-05-04T00:00:00.000Z", updatedAt: now, }, ], exceptions: [], invites: [], syncStatuses: [ { id: "sync_dctouch_client_authentik", objectId: "client_romashka", objectName: "DCTOUCH", objectType: "client", target: "authentik", state: "synced", lastSyncAt: now, error: null, updatedAt: now, }, { id: "sync_dc_touch_authentik", objectId: "user_root", objectName: "dcctouch@gmail.com", objectType: "user", target: "authentik", state: dcTouchAuthentikUserId ? "synced" : "pending", lastSyncAt: dcTouchAuthentikUserId ? now : null, error: dcTouchAuthentikUserId ? null : "Пользователь есть в Authentik, но Launcher seed ещё не содержит Authentik UUID.", updatedAt: now, }, { id: "sync_silver_psih_authentik", objectId: "user_silver_psih", objectName: "silver_psih@yahoo.com", objectType: "user", target: "authentik", state: silverPsihAuthentikUserId ? "synced" : "pending", lastSyncAt: silverPsihAuthentikUserId ? now : null, error: silverPsihAuthentikUserId ? null : "Пользователь найден в Plane, но ещё не создан в Authentik через Launcher invite/sync flow.", updatedAt: now, }, { id: "sync_dctouch_groups_authentik", objectId: "client_romashka:groups", objectName: "DCTOUCH groups", objectType: "group", target: "authentik", state: "pending", lastSyncAt: null, error: null, updatedAt: now, }, { id: "sync_task_manager_authentik", objectId: "service_task_manager", objectName: "OPERATIONAL CORE", objectType: "service", target: "authentik", state: "synced", lastSyncAt: now, error: null, updatedAt: now, }, ], auditEvents: [ { id: "audit_live_seed_control_plane", at: now, actorUserId: "system", actorName: "NODE.DC seed", action: "Применён live seed control-plane", objectType: "control_plane", objectName: "Launcher users and access", clientId: "client_romashka", result: "success", details: "Demo-участники удалены из runtime storage. Оставлены dcctouch@gmail.com и silver_psih@yahoo.com.", }, ], services, }; await writeJson(dataPath, liveData); console.log(`Seeded ${liveData.users.length} users, ${liveData.clients.length} client, ${liveData.groups.length} groups.`); function readJson(path) { if (!existsSync(path)) { return {}; } return JSON.parse(readFileSync(path, "utf8")); } async function writeJson(path, data) { await mkdir(dirname(path), { recursive: true }); await writeFile(path, `${JSON.stringify(data, null, 2)}\n`, "utf8"); } function resolveStorageRoot(projectRoot) { const configuredRoot = process.env.NODEDC_LAUNCHER_STORAGE_DIR; if (configuredRoot && configuredRoot.trim()) { return isAbsolute(configuredRoot) ? configuredRoot : resolve(projectRoot, configuredRoot); } return join(projectRoot, "server", "storage"); }