diff --git a/manager/scratch/find_unused_files.cjs b/manager/scratch/find_unused_files.cjs new file mode 100644 index 0000000..5b5079a --- /dev/null +++ b/manager/scratch/find_unused_files.cjs @@ -0,0 +1,97 @@ +const pg = require('pg'); +const { S3Client, ListBucketsCommand, ListObjectsV2Command } = require('@aws-sdk/client-s3'); + +const pool = new pg.Pool({ + connectionString: 'postgresql://edumanager:EduManager2026!Seguro@150.230.87.131:5432/edumanager' +}); + +const s3Client = new S3Client({ + endpoint: 'http://150.230.87.131:9000', + region: 'us-east-1', + credentials: { + accessKeyId: 'minioadmin', + secretAccessKey: 'MiniO2026!Seguro', + }, + forcePathStyle: true, +}); + +async function main() { + try { + console.log('--- 1. BUSCANDO TODAS AS TABELAS E DADOS DO POSTGRESQL ---'); + const { rows: tables } = await pool.query("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'"); + console.log(`Encontradas ${tables.length} tabelas no banco de dados.`); + + let dbContentDump = ''; + for (const t of tables) { + const tableName = t.table_name; + try { + const { rows } = await pool.query(`SELECT * FROM "${tableName}"`); + dbContentDump += JSON.stringify(rows) + ' '; + } catch (err) { + console.warn(`Aviso: não foi possível ler a tabela ${tableName}:`, err.message); + } + } + console.log(`Dumping de referências concluído (${(dbContentDump.length / 1024 / 1024).toFixed(2)} MB).`); + + console.log('\n--- 2. LISTANDO ARQUIVOS NO STORAGE MINIO ---'); + const bucketsData = await s3Client.send(new ListBucketsCommand({})); + const buckets = bucketsData.Buckets || []; + console.log(`Encontrados ${buckets.length} buckets no MinIO.`); + + const allFiles = []; + let totalStorageSize = 0; + + for (const bucket of buckets) { + try { + const objects = await s3Client.send(new ListObjectsV2Command({ Bucket: bucket.Name })); + const contents = objects.Contents || []; + console.log(`Bucket [${bucket.Name}]: ${contents.length} arquivos encontrados.`); + for (const obj of contents) { + totalStorageSize += obj.Size || 0; + allFiles.push({ + bucket: bucket.Name, + key: obj.Key, + size: obj.Size || 0, + url: `/storage/${bucket.Name}/${obj.Key}` + }); + } + } catch (err) { + console.warn(`Não foi possível listar objetos do bucket ${bucket.Name}:`, err.message); + } + } + console.log(`Total geral no MinIO: ${(totalStorageSize / 1024 / 1024).toFixed(2)} MB (${allFiles.length} arquivos).`); + + console.log('\n--- 3. FILTRANDO ARQUIVOS NÃO UTILIZADOS ---'); + const unusedFiles = []; + const usedFiles = []; + + for (const file of allFiles) { + const hasReference = dbContentDump.includes(file.key) || dbContentDump.includes(file.url); + if (hasReference) { + usedFiles.push(file); + } else { + unusedFiles.push(file); + } + } + + const totalUnusedSize = unusedFiles.reduce((acc, curr) => acc + curr.size, 0); + console.log(`\nRESULTADOS:`); + console.log(`- Arquivos em Uso: ${usedFiles.length}`); + console.log(`- Arquivos Órfãos (NÃO usados): ${unusedFiles.length}`); + console.log(`- Espaço sendo desperdiçado: ${(totalUnusedSize / 1024 / 1024).toFixed(2)} MB`); + + if (unusedFiles.length > 0) { + console.log('\n--- LISTA DE ARQUIVOS NÃO UTILIZADOS ---'); + unusedFiles.forEach((file, index) => { + console.log(`${index + 1}. Bucket: [${file.bucket}] | Arquivo: ${file.key} | Tamanho: ${(file.size / 1024).toFixed(2)} KB`); + }); + } else { + console.log('\nExcelente! Todos os arquivos armazenados no MinIO estão sendo referenciados no banco de dados!'); + } + } catch (error) { + console.error('Erro na execução:', error); + } finally { + await pool.end(); + } +} +main(); diff --git a/manager/scratch/find_unused_files_internal.cjs b/manager/scratch/find_unused_files_internal.cjs new file mode 100644 index 0000000..9d6ae6c --- /dev/null +++ b/manager/scratch/find_unused_files_internal.cjs @@ -0,0 +1,101 @@ +const pg = require('pg'); +const { S3Client, ListBucketsCommand, ListObjectsV2Command } = require('@aws-sdk/client-s3'); + +const DATABASE_URL = process.env.DATABASE_URL || 'postgresql://edumanager:EduManager2026!Seguro@postgres:5432/edumanager'; +const pool = new pg.Pool({ connectionString: DATABASE_URL }); + +const MINIO_ENDPOINT = process.env.MINIO_ENDPOINT || 'minio'; +const MINIO_PORT = process.env.MINIO_PORT || '9000'; +const MINIO_ACCESS_KEY = process.env.MINIO_ACCESS_KEY || 'minioadmin'; +const MINIO_SECRET_KEY = process.env.MINIO_SECRET_KEY || 'MiniO2026!Seguro'; + +const s3Client = new S3Client({ + endpoint: `http://${MINIO_ENDPOINT}:${MINIO_PORT}`, + region: 'us-east-1', + credentials: { + accessKeyId: MINIO_ACCESS_KEY, + secretAccessKey: MINIO_SECRET_KEY, + }, + forcePathStyle: true, +}); + +async function main() { + try { + console.log('--- 1. BUSCANDO TODAS AS TABELAS E DADOS DO POSTGRESQL ---'); + const { rows: tables } = await pool.query("SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'"); + console.log(`Encontradas ${tables.length} tabelas no banco de dados.`); + + let dbContentDump = ''; + for (const t of tables) { + const tableName = t.table_name; + try { + const { rows } = await pool.query(`SELECT * FROM "${tableName}"`); + dbContentDump += JSON.stringify(rows) + ' '; + } catch (err) { + console.warn(`Aviso: não foi possível ler a tabela ${tableName}:`, err.message); + } + } + console.log(`Dumping de referências concluído (${(dbContentDump.length / 1024 / 1024).toFixed(2)} MB).`); + + console.log('\n--- 2. LISTANDO ARQUIVOS NO STORAGE MINIO ---'); + const bucketsData = await s3Client.send(new ListBucketsCommand({})); + const buckets = bucketsData.Buckets || []; + console.log(`Encontrados ${buckets.length} buckets no MinIO.`); + + const allFiles = []; + let totalStorageSize = 0; + + for (const bucket of buckets) { + try { + const objects = await s3Client.send(new ListObjectsV2Command({ Bucket: bucket.Name })); + const contents = objects.Contents || []; + console.log(`Bucket [${bucket.Name}]: ${contents.length} arquivos encontrados.`); + for (const obj of contents) { + totalStorageSize += obj.Size || 0; + allFiles.push({ + bucket: bucket.Name, + key: obj.Key, + size: obj.Size || 0, + url: `/storage/${bucket.Name}/${obj.Key}` + }); + } + } catch (err) { + console.warn(`Não foi possível listar objetos do bucket ${bucket.Name}:`, err.message); + } + } + console.log(`Total geral no MinIO: ${(totalStorageSize / 1024 / 1024).toFixed(2)} MB (${allFiles.length} arquivos).`); + + console.log('\n--- 3. FILTRANDO ARQUIVOS NÃO UTILIZADOS ---'); + const unusedFiles = []; + const usedFiles = []; + + for (const file of allFiles) { + const hasReference = dbContentDump.includes(file.key) || dbContentDump.includes(file.url); + if (hasReference) { + usedFiles.push(file); + } else { + unusedFiles.push(file); + } + } + + const totalUnusedSize = unusedFiles.reduce((acc, curr) => acc + curr.size, 0); + console.log(`\nRESULTADOS:`); + console.log(`- Arquivos em Uso: ${usedFiles.length}`); + console.log(`- Arquivos Órfãos (NÃO usados): ${unusedFiles.length}`); + console.log(`- Espaço sendo desperdiçado: ${(totalUnusedSize / 1024 / 1024).toFixed(2)} MB`); + + if (unusedFiles.length > 0) { + console.log('\n--- LISTA DE ARQUIVOS NÃO UTILIZADOS ---'); + unusedFiles.forEach((file, index) => { + console.log(`${index + 1}. Bucket: [${file.bucket}] | Arquivo: ${file.key} | Tamanho: ${(file.size / 1024).toFixed(2)} KB`); + }); + } else { + console.log('\nExcelente! Todos os arquivos armazenados no MinIO estão sendo referenciados no banco de dados!'); + } + } catch (error) { + console.error('Erro na execução:', error); + } finally { + await pool.end(); + } +} +main(); diff --git a/manager/scratch/fix_campos_check.cjs b/manager/scratch/fix_campos_check.cjs new file mode 100644 index 0000000..67c5e13 --- /dev/null +++ b/manager/scratch/fix_campos_check.cjs @@ -0,0 +1,27 @@ +const pg = require('pg'); + +const pool = new pg.Pool({ + connectionString: 'postgresql://edumanager:EduManager2026!Seguro@150.230.87.131:5432/edumanager' +}); + +async function run() { + try { + console.log('Removendo CHECK constraint antigo...'); + await pool.query('ALTER TABLE prematricula_campos DROP CONSTRAINT prematricula_campos_tipo_check'); + + console.log('Adicionando CHECK constraint atualizado com file e banner...'); + await pool.query(` + ALTER TABLE prematricula_campos + ADD CONSTRAINT prematricula_campos_tipo_check + CHECK (tipo = ANY (ARRAY['text', 'email', 'phone', 'cpf', 'date', 'select', 'textarea', 'number', 'file', 'banner'])) + `); + + console.log('✅ Constraint atualizado com sucesso! Tipos file e banner agora são aceitos.'); + } catch (err) { + console.error('Erro:', err.message); + } finally { + await pool.end(); + } +} + +run(); diff --git a/manager/server.selfhosted.js b/manager/server.selfhosted.js index 17caa0b..f0abb3f 100644 --- a/manager/server.selfhosted.js +++ b/manager/server.selfhosted.js @@ -111,7 +111,7 @@ const app=document.getElementById('app'); function maskPhone(v){ if(!v)return ""; - v=v.replace(/\D/g,""); + v=v.replace(/[^0-9]/g,""); if(v.length>11) v=v.substring(0,11); if(v.length>10){ return '(' + v.substring(0,2) + ') ' + v.substring(2,7) + '-' + v.substring(7); @@ -125,7 +125,7 @@ function maskPhone(v){ function maskCPF(v){ if(!v)return ""; - v=v.replace(/\D/g,""); + v=v.replace(/[^0-9]/g,""); if(v.length>11) v=v.substring(0,11); if(v.length>9){ return v.substring(0,3) + '.' + v.substring(3,6) + '.' + v.substring(6,9) + '-' + v.substring(9); @@ -139,7 +139,7 @@ function maskCPF(v){ function maskCEP(v){ if(!v)return ""; - v=v.replace(/\D/g,""); + v=v.replace(/[^0-9]/g,""); if(v.length>8) v=v.substring(0,8); if(v.length>5){ return v.substring(0,5) + '-' + v.substring(5); @@ -147,25 +147,30 @@ function maskCEP(v){ return v; } -// Global fallback event listener +// Global single-scope event listener (Rule 50 - anti-double-bind) +var _maskingActive = false; document.addEventListener('input',function(e){ + if(_maskingActive) return; if(!e.target)return; - const dt=e.target.getAttribute('data-type'); + var dt=e.target.getAttribute('data-type'); if(dt==='phone' || dt==='cpf' || dt==='cep'){ - const originalVal = e.target.value; - let newVal = originalVal; - if(dt==='phone') newVal = maskPhone(originalVal); - else if(dt==='cpf') newVal = maskCPF(originalVal); - else if(dt==='cep') newVal = maskCEP(originalVal); + _maskingActive = true; + var raw = e.target.value; + var masked = raw; + if(dt==='phone') masked = maskPhone(raw); + else if(dt==='cpf') masked = maskCPF(raw); + else if(dt==='cep') masked = maskCEP(raw); - if (newVal !== originalVal) { - const start = e.target.selectionStart; - e.target.value = newVal; + if (masked !== raw) { + var cursorPos = e.target.selectionStart || 0; + var diff = masked.length - raw.length; + e.target.value = masked; try { - const diff = newVal.length - originalVal.length; - e.target.setSelectionRange(start + diff, start + diff); + var newPos = Math.max(0, cursorPos + diff); + e.target.setSelectionRange(newPos, newPos); } catch(err) {} } + _maskingActive = false; } });