@@ -0,0 +1,115 @@
|
||||
<?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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user