Files
Investidor-V2/app/Services/OpportunityManagementInstallation.php
T
nala 01405cbf8a
tests / ci (push) Canceled after 0s
oportunidades 5
2026-10-10 14:15:28 +02:00

62 lines
4.7 KiB
PHP

<?php
namespace App\Services;
use App\Models\User;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\DB;
class OpportunityManagementInstallation
{
public const MIGRATION = '2026_10_09_020000_extend_opportunity_management';
public function fingerprint(): string
{
$files = [__FILE__, app_path('Models/Opportunity.php'), app_path('Services/OpportunityManagement.php'), app_path('Http/Controllers/OpportunityManagementController.php'), app_path('Http/Controllers/PortalManagementController.php'), database_path('migrations/'.self::MIGRATION.'.php'), base_path('routes/web.php'), resource_path('js/pages/management/opportunity-draft.tsx'), resource_path('js/pages/management/opportunities.tsx')];
$files[] = app_path('Services/OpportunityMaturity.php');
$files = [...$files, app_path('Services/OpportunityRealEstateDraft.php'), app_path('Http/Middleware/HandleInertiaRequests.php'), config_path('portal.php'),
resource_path('data/investmoz-opportunity-modular.schema.json'), resource_path('js/pages/dashboard.tsx'), resource_path('js/components/app-sidebar.tsx'), resource_path('js/types/opportunity-management.ts'), base_path('vite.config.ts')];
$files = [...$files, resource_path('js/lib/portal-navigation.ts'), resource_path('js/components/portal-grouped-menu.tsx')];
$files = [...$files, ...PortalProfileAccess::fingerprintFiles()];
return hash('sha256', json_encode(array_map(fn ($file) => hash_file('sha256', $file), $files), JSON_THROW_ON_ERROR));
}
/** @return array<string,mixed> */
public function preview(): array
{
$path = storage_path('app/private/investmoz-v2/opportunity-management-activation-proof.json');
$proof = is_file($path) ? json_decode(file_get_contents($path) ?: '{}', true) : [];
$identity = DB::selectOne('SELECT current_database() AS database,inet_server_port() AS port');
$code = $this->fingerprint();
$ready = ($proof['restore_verified'] ?? false) && ($proof['compatibility_verified'] ?? false) && ($proof['reversal_verified'] ?? false) && ($proof['qa_passed'] ?? false) && ($proof['code_hash'] ?? '') === $code && ($proof['target']['database'] ?? '') === $identity->database && (int) ($proof['target']['port'] ?? 0) === (int) $identity->port && strtotime($proof['verified_at'] ?? '') >= time() - 86400 && is_file($proof['dump_path'] ?? '') && hash_file('sha256', $proof['dump_path']) === ($proof['sha256'] ?? null);
$installed = app(OpportunityManagement::class)->installed();
return ['installed' => $installed, 'ready' => $ready, 'target' => $identity, 'code_hash' => $code, 'hash' => hash('sha256', json_encode([$identity, $code, $ready, $installed], JSON_THROW_ON_ERROR)), 'migration' => self::MIGRATION, 'backupDate' => $proof['verified_at'] ?? null, 'changes' => ['Permitir setor, título e moeda ausentes em rascunhos; nenhum preenchimento fictício.', 'Acrescentar versão, categoria opcional, rascunho de revisão, chave idempotente e marca de primeira submissão à entidade existente.', 'Impedir eliminação física de versões submetidas; preservar relações e classificações.', 'Nenhuma submissão, aprovação ou publicação automática.'], 'rollback' => 'Down apenas antes de novos eventos/rascunhos incompletos. Depois preservar e reconciliar dados; restauro integral requer janela autorizada e reconciliação de alterações posteriores.'];
}
public function apply(User $actor, string $hash): void
{
abort_unless($actor->can('manage-portal'), 403);
DB::transaction(function () use ($actor, $hash) {
DB::select('SELECT pg_advisory_xact_lock(91026, 100000)');
$preview = $this->preview();
abort_unless(hash_equals($preview['hash'], $hash), 409, 'Plano mudou; reveja.');
if ($preview['installed']) {
return;
}
abort_unless($preview['ready'], 422, 'Backup/restauro, QA, compatibilidade e reversão desta versão necessários.');
config(['opportunity_management.authenticated_installation' => true]);
try {
$exit = Artisan::call('migrate', ['--path' => 'database/migrations/'.self::MIGRATION.'.php', '--force' => true]);
abort_unless($exit === 0, 500, 'Migração falhou.');
} finally {
config(['opportunity_management.authenticated_installation' => false]);
}
DB::table('audit_events')->insert(['source_system' => 'opportunity-management-install', 'entity_type' => 'installation', 'action' => 'opportunity_management_installed', 'actor_user_id' => $actor->id, 'after_data' => json_encode(['code_hash' => $preview['code_hash'], 'migration' => self::MIGRATION], JSON_THROW_ON_ERROR)]);
});
}
}