116 lines
5.4 KiB
PHP
116 lines
5.4 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Schema;
|
|
use Illuminate\Support\Str;
|
|
use Throwable;
|
|
|
|
class CheckDocumentLibrarySchema extends Command
|
|
{
|
|
protected $signature = 'portal:check-document-library-schema';
|
|
|
|
protected $description = 'Confere o esquema e as migrações da biblioteca documental antes da activação';
|
|
|
|
public function handle(): int
|
|
{
|
|
$reference = (string) Str::uuid();
|
|
|
|
try {
|
|
$required = [
|
|
'portal_resources' => [
|
|
'id', 'document_id', 'title', 'category', 'topic', 'legislation', 'essential', 'published',
|
|
'source_reference', 'rights_status', 'description', 'document_type', 'document_number',
|
|
'document_date', 'source_url', 'language', 'version_label', 'document_group_id',
|
|
'applicable_from', 'applicable_until',
|
|
'territorial_scope', 'territory_id', 'source_confirmation', 'legal_status', 'last_reviewed_at',
|
|
'document_identity_key', 'duplicate_of_resource_id', 'metadata_version', 'created_by',
|
|
'last_reviewed_by', 'independent_review',
|
|
],
|
|
'documents' => [
|
|
'id', 'status', 'data_classification', 'sha256', 'deleted_at', 'file_name', 'media_type',
|
|
'storage_key', 'size_bytes', 'version_no', 'issuer', 'issued_on', 'valid_until', 'uploaded_by',
|
|
'rights_status', 'original_source', 'malware_scan_status', 'malware_scanned_sha256',
|
|
'malware_engine', 'malware_engine_version', 'malware_signatures_at', 'malware_scanned_at',
|
|
'malware_failure_reason', 'malware_scan_attempts',
|
|
],
|
|
'portal_resource_links' => ['id', 'resource_id', 'target_type', 'target_key', 'relationship', 'applicability'],
|
|
'portal_resource_revisions' => ['id', 'resource_id', 'revision_no', 'change_type', 'snapshot'],
|
|
'document_malware_scans' => ['id', 'document_id', 'file_sha256', 'result', 'engine', 'engine_version', 'started_at'],
|
|
];
|
|
|
|
$missing = [];
|
|
foreach ($required as $table => $columns) {
|
|
if (! Schema::hasTable($table)) {
|
|
$missing[] = "tabela {$table}";
|
|
continue;
|
|
}
|
|
|
|
foreach (array_diff($columns, Schema::getColumnListing($table)) as $column) {
|
|
$missing[] = "{$table}.{$column}";
|
|
}
|
|
}
|
|
|
|
$requiredMigrations = [
|
|
'2026_10_05_120000_extend_portal_document_catalogue',
|
|
'2026_10_05_130000_add_document_malware_scanning',
|
|
];
|
|
if (! Schema::hasTable('migrations')) {
|
|
$missing[] = 'repositório de migrações';
|
|
} else {
|
|
$ran = DB::table('migrations')->whereIn('migration', $requiredMigrations)->pluck('migration')->all();
|
|
foreach (array_diff($requiredMigrations, $ran) as $migration) {
|
|
$missing[] = "migração {$migration}";
|
|
}
|
|
}
|
|
|
|
if ($missing !== []) {
|
|
$this->error('Esquema da biblioteca incompatível. Não activar o catálogo; faltam elementos de esquema ou migrações.');
|
|
foreach ($missing as $item) {
|
|
$this->line('- '.$item);
|
|
}
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
foreach ([
|
|
['portal_resources', 'rights_status', 'unconfirmed'],
|
|
['documents', 'rights_status', 'unconfirmed'],
|
|
['documents', 'malware_scan_status', 'pending'],
|
|
] as [$table, $column, $expectedDefault]) {
|
|
$default = DB::selectOne(
|
|
'select column_default from information_schema.columns where table_schema = current_schema() and table_name = ? and column_name = ?',
|
|
[$table, $column],
|
|
)?->column_default;
|
|
if (! is_string($default) || ! str_contains(strtolower($default), $expectedDefault)) {
|
|
$this->error("Valor predefinido inseguro ou ausente em {$table}.{$column}; preservar os registos e rever a migração.");
|
|
|
|
return self::FAILURE;
|
|
}
|
|
}
|
|
|
|
$invalidCleanRecords = DB::table('documents')->where('malware_scan_status', 'clean')
|
|
->where(function ($query): void {
|
|
$query->whereNull('malware_scanned_sha256')->orWhereColumn('malware_scanned_sha256', '!=', 'sha256');
|
|
})->count();
|
|
if ($invalidCleanRecords > 0) {
|
|
$this->error('Existem documentos marcados como limpos sem correspondência do hash analisado.');
|
|
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$this->info('Esquema da biblioteca compatível; verificações de direitos e análise antimalware disponíveis.');
|
|
|
|
return self::SUCCESS;
|
|
} catch (Throwable $exception) {
|
|
Log::error('Document library schema compatibility check failed.', ['reference' => $reference, 'exception' => $exception]);
|
|
$this->error('Não foi possível verificar a compatibilidade do esquema. Consulte o registo interno. Referência: '.$reference);
|
|
|
|
return self::FAILURE;
|
|
}
|
|
}
|
|
}
|