56 lines
4.9 KiB
PHP
56 lines
4.9 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use App\Models\User;
|
|
use Illuminate\Support\Facades\Artisan;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class OpportunityResourceInstallation
|
|
{
|
|
public const MIGRATION = '2026_10_09_030000_extend_opportunity_resources';
|
|
|
|
public function fingerprint(): string
|
|
{
|
|
$files = [__FILE__, app_path('Services/OpportunityImageProcessor.php'), app_path('Services/OpportunityResources.php'), app_path('Http/Controllers/OpportunityResourceController.php'), app_path('Http/Controllers/OpportunityManagementController.php'), app_path('Jobs/ScanDocumentForMalware.php'), database_path('migrations/'.self::MIGRATION.'.php'), base_path('routes/web.php'), resource_path('js/pages/management/opportunity-resources.tsx'), resource_path('js/pages/management/opportunity-draft.tsx'), resource_path('js/pages/management/opportunity-install.tsx')];
|
|
|
|
$files = [...$files, app_path('Services/CountryCatalogue.php'), app_path('Services/DeclaredOriginStatistics.php'), app_path('Services/VirtualDesk.php'), app_path('Http/Controllers/CrmController.php'), app_path('Http/Controllers/InvestmentWorkspaceController.php'), app_path('Http/Controllers/InvestmentSubmissionController.php'), app_path('Http/Controllers/InvestmentDiscoveryController.php'), app_path('Http/Controllers/VirtualDeskController.php'), resource_path('data/iso3166.tab'), resource_path('js/components/country-select.tsx'), resource_path('js/components/investment-territory.tsx'), resource_path('js/pages/investment/profile.tsx'), resource_path('js/pages/investment/submission.tsx'), resource_path('js/pages/investment/catalogue.tsx'), resource_path('js/pages/desk/request.tsx'), resource_path('js/pages/desk/panel.tsx'), resource_path('js/pages/crm/leads.tsx')];
|
|
|
|
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-resources-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(OpportunityResources::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' => ['Recursos privados de preparação ligados às oportunidades existentes, documents e geo_locations; nenhum dado existente alterado.', 'Originais cifrados; scan pendente bloqueia acesso aos binários, não preparação de metadados/localizações.', 'Versões, exclusão lógica e restauro com auditoria; nenhuma publicação automática.', 'Pendentes: ClamD real, derivadas seguras/revisão/publicação e compatibilidade com limites oficiais onde ausentes.'], 'rollback' => 'Reversão apenas com tabelas vazias e sem documentos cifrados; após utilização, preservar e reconciliar versões antes de uma ação autorizada.'];
|
|
}
|
|
|
|
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)');
|
|
$p = $this->preview();
|
|
abort_unless(hash_equals($p['hash'], $hash), 409, 'Plano alterado; reveja.');
|
|
if ($p['installed']) {
|
|
return;
|
|
}
|
|
abort_unless($p['ready'], 422, 'Provas desta versão necessárias.');
|
|
config(['opportunity_resources.authenticated_installation' => true]);
|
|
try {
|
|
abort_unless(Artisan::call('migrate', ['--path' => 'database/migrations/'.self::MIGRATION.'.php', '--force' => true]) === 0, 500);
|
|
} finally {
|
|
config(['opportunity_resources.authenticated_installation' => false]);
|
|
}
|
|
DB::table('audit_events')->insert(['source_system' => 'opportunity-resources-install', 'entity_type' => 'installation', 'action' => 'opportunity_resources_installed', 'actor_user_id' => $actor->id, 'after_data' => json_encode(['code_hash' => $p['code_hash'], 'migration' => self::MIGRATION])]);
|
|
});
|
|
}
|
|
}
|