import { randomUUID } from "node:crypto"; import { createSequelize } from "@erp/core/api"; import { BcryptPasswordHasher } from "@erp/identity/api"; import { IdentityPermissionCodes } from "@erp/identity/common"; import type { ILogger } from "@repo/rdx-logger"; import type { Sequelize } from "sequelize"; import type { E2ESchemaDatabaseConfig } from "./e2e-schema-database-config"; import { assertE2ESchemaDatabaseConfig } from "./e2e-schema-database-config"; export interface E2EBootstrapConfig { readonly executionId: string; readonly email: string; readonly password: string; readonly companyName: string; readonly companySlug: string; } export interface E2EBootstrapResult { readonly accountId: string; readonly companyId: string; readonly companySlug: string; readonly email: string; } /** Crea el grafo mínimo autenticable sobre un schema E2E ya preparado. */ export async function bootstrapE2EData( databaseConfig: E2ESchemaDatabaseConfig, config: E2EBootstrapConfig, dependencies: { readonly logger: ILogger; readonly createDatabase?: (config: E2ESchemaDatabaseConfig) => Promise; } ): Promise { assertE2ESchemaDatabaseConfig(databaseConfig); assertBootstrapConfig(config, databaseConfig.executionId); const database = await (dependencies.createDatabase ?? createDatabase)( databaseConfig, dependencies.logger ); try { return await createBootstrapData(database, config); } finally { await database.close(); } } async function createBootstrapData( database: Sequelize, config: E2EBootstrapConfig ): Promise { const passwordHashResult = await new BcryptPasswordHasher({ rounds: 10 }).hash(config.password); if (passwordHashResult.isFailure) { throw passwordHashResult.error; } const accountId = randomUUID(); const companyId = randomUUID(); const membershipId = randomUUID(); const roleId = randomUUID(); const onboardingId = randomUUID(); await database.transaction(async (transaction) => { const query = (sql: string, replacements: Record) => database.query(sql, { replacements, transaction }); await query( "INSERT INTO identity_accounts (id, email, password_hash, name, avatar_url, language_code, status, created_at, updated_at) VALUES (:id, :email, :passwordHash, :name, NULL, 'es', 'active', NOW(), NOW())", { id: accountId, email: config.email, passwordHash: passwordHashResult.data.toPrimitive(), name: "E2E User", } ); await query( "INSERT INTO companies (id, legal_name, slug, status, created_at, updated_at) VALUES (:id, :name, :slug, 'active', NOW(), NOW())", { id: companyId, name: config.companyName, slug: config.companySlug } ); await query( "INSERT INTO identity_company_memberships (id, account_id, company_id, status, created_at, updated_at) VALUES (:id, :accountId, :companyId, 'active', NOW(), NOW())", { id: membershipId, accountId, companyId } ); await query( "INSERT INTO identity_roles (id, company_id, code, name, is_system, permission_codes, created_at, updated_at) VALUES (:id, :companyId, 'admin', 'Administrator', true, :permissions, NOW(), NOW())", { id: roleId, companyId, permissions: JSON.stringify(Object.values(IdentityPermissionCodes)) } ); await query( "INSERT INTO identity_company_membership_roles (membership_id, role_id) VALUES (:membershipId, :roleId)", { membershipId, roleId } ); await query( "INSERT INTO initial_onboardings (id, account_id, status, company_id, created_at, updated_at, completed_at) VALUES (:id, :accountId, 'completed', :companyId, NOW(), NOW(), NOW())", { id: onboardingId, accountId, companyId } ); }); return { accountId, companyId, companySlug: config.companySlug, email: config.email }; } async function createDatabase( config: E2ESchemaDatabaseConfig, logger: ILogger ): Promise { return await createSequelize( { dialect: "mysql", host: config.host, port: config.port, name: config.database, user: config.username, password: config.password, logging: false, ssl: false, timezone: "Europe/Madrid", }, logger ); } function assertBootstrapConfig(config: E2EBootstrapConfig, executionId: string): void { if ( config.executionId !== executionId || !config.email || !config.password || !config.companyName || !config.companySlug ) { throw new Error("Invalid E2E bootstrap configuration."); } }