Claude-skill-registry db-guardian
Especialista Sênior em MongoDB, Segurança de Dados, Migrations, Backup/Recovery e Data Integrity. Guardian dos dados do Super Cartola Manager com foco em operações seguras, auditoria de schemas, otimização de queries e gestão de lifecycle de dados. Use para migrations, limpeza, manutenção, snapshots, índices, validações e qualquer operação crítica com banco de dados.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/db-guardian" ~/.claude/skills/majiayu000-claude-skill-registry-db-guardian && rm -rf "$T"
manifest:
skills/data/db-guardian/SKILL.mdsource content
DB Guardian Skill (MongoDB Master Edition)
🎯 Missão
Proteger a integridade dos dados do Super Cartola Manager através de operações seguras, backup estratégico, migrations controladas e monitoramento proativo.
1. 🛡️ Protocolo de Segurança Máxima (Data Safety)
1.1 Regras Invioláveis
NUNCA fazer sem backup prévio:
em qualquer collectiondeleteMany()
de collections ou databasedrop()
sem filtro específicoupdateMany()
em documentos críticosreplaceOne()- Qualquer operação que modifique > 100 documentos
Collections INTOCÁVEIS (nunca deletar/resetar):
const PROTECTED_COLLECTIONS = [ 'users', // Contas de acesso 'times', // Identidade visual/nomes 'system_config', // Configurações globais 'ligas', // Definições de ligas 'audit_logs' // Histórico de auditoria ];
1.2 Checklist Pré-Operação Destrutiva
□ Backup criado e verificado □ Operação testada em ambiente de dev/staging □ Filtros validados (liga_id, temporada, etc) □ Rollback plan documentado □ Aprovação do tech lead (se produção) □ Horário de baixa atividade escolhido □ Monitoramento ativo preparado
1.3 Pattern de Backup Obrigatório
// SEMPRE antes de operações destrutivas async function backupBeforeOperation(collection, filter, operationName) { const timestamp = new Date().toISOString().replace(/[:.]/g, '-'); const backupPath = `data/backups/${operationName}_${timestamp}.json`; // 1. Extrair dados const data = await db.collection(collection).find(filter).toArray(); // 2. Salvar backup fs.writeFileSync( backupPath, JSON.stringify({ timestamp: new Date(), collection, filter, operation: operationName, count: data.length, data }, null, 2) ); // 3. Verificar integridade const backup = JSON.parse(fs.readFileSync(backupPath, 'utf8')); if (backup.count !== data.length) { throw new Error('Backup integrity check failed'); } console.log(`✅ Backup criado: ${backupPath} (${data.length} docs)`); return backupPath; } // Exemplo de uso const backupPath = await backupBeforeOperation( 'rodadas', { liga_id: ligaId, temporada: '2025' }, 'cleanup_2025' ); // Só então executar operação await db.collection('rodadas').deleteMany({ liga_id: ligaId, temporada: '2025' });
2. 🔄 Virada de Temporada (Season Turnover)
2.1 Arquitetura de Dados por Temporada
data/ ├── history/ │ ├── 2025/ │ │ ├── metadata.json # Metadados da temporada │ │ ├── final_standings.json # Classificação final │ │ ├── financial_summary.json # Resumo financeiro │ │ ├── champions.json # Campeões de cada disputa │ │ └── participants.json # Lista de participantes │ └── 2026/ │ └── ... (mesmo padrão) ├── backups/ │ └── YYYY-MM-DD_HH-MM-SS/ │ └── ... (backups automáticos) └── users_registry.json # Cartório vitalício
2.2 Script de Virada de Temporada (Turn Key)
Criar
/scripts/turn_key_2026.js:
const mongoose = require('mongoose'); const fs = require('fs'); const path = require('path'); // ==================== CONFIGURAÇÃO ==================== const CURRENT_SEASON = '2025'; const NEW_SEASON = '2026'; const TURNOVER_DATE = new Date('2026-01-01T00:00:00Z'); // Trava de segurança por data if (Date.now() < TURNOVER_DATE.getTime()) { console.error('🔴 ERRO: Virada de temporada só pode ser executada após 01/01/2026'); console.error(`Data atual: ${new Date().toISOString()}`); console.error(`Data permitida: ${TURNOVER_DATE.toISOString()}`); process.exit(1); } // ==================== CONEXÃO ==================== const MONGO_URI = process.env.MONGO_URI; async function connectDB() { try { await mongoose.connect(MONGO_URI); console.log('✅ Conectado ao MongoDB'); } catch (error) { console.error('🔴 Erro ao conectar:', error); process.exit(1); } } // ==================== SNAPSHOT FINAL ==================== async function createSeasonSnapshot(season) { const snapshotPath = `data/history/${season}`; // Criar diretório se não existir if (!fs.existsSync(snapshotPath)) { fs.mkdirSync(snapshotPath, { recursive: true }); } console.log(`📸 Criando snapshot da temporada ${season}...`); // 1. Metadados const metadata = { season, snapshotDate: new Date(), totalParticipants: await mongoose.model('Participante').countDocuments({ temporada: season }), totalRodadas: await mongoose.model('Rodada').countDocuments({ temporada: season }) }; fs.writeFileSync( path.join(snapshotPath, 'metadata.json'), JSON.stringify(metadata, null, 2) ); // 2. Classificação Final (todas ligas) const ligas = await mongoose.model('Liga').find({}).lean(); const finalStandings = {}; for (const liga of ligas) { const ranking = await mongoose.model('Participante') .find({ liga_id: liga._id, temporada: season }) .select('nome pontos_acumulados posicao_final') .sort({ pontos_acumulados: -1 }) .lean(); finalStandings[liga.nome] = ranking; } fs.writeFileSync( path.join(snapshotPath, 'final_standings.json'), JSON.stringify(finalStandings, null, 2) ); // 3. Resumo Financeiro const financialSummary = await createFinancialSummary(season); fs.writeFileSync( path.join(snapshotPath, 'financial_summary.json'), JSON.stringify(financialSummary, null, 2) ); // 4. Campeões const champions = await extractChampions(season); fs.writeFileSync( path.join(snapshotPath, 'champions.json'), JSON.stringify(champions, null, 2) ); console.log(`✅ Snapshot completo salvo em ${snapshotPath}`); } async function createFinancialSummary(season) { const summary = { season, totalCredito: 0, totalDebito: 0, saldoGeral: 0, porLiga: {} }; const ligas = await mongoose.model('Liga').find({}).lean(); for (const liga of ligas) { const participantes = await mongoose.model('Participante') .find({ liga_id: liga._id, temporada: season }) .lean(); let credito = 0; let debito = 0; participantes.forEach(p => { const saldo = p.saldo_temporada || 0; if (saldo > 0) credito += saldo; else debito += Math.abs(saldo); }); summary.porLiga[liga.nome] = { credito, debito, saldo: credito - debito, participantes: participantes.length }; summary.totalCredito += credito; summary.totalDebito += debito; } summary.saldoGeral = summary.totalCredito - summary.totalDebito; return summary; } async function extractChampions(season) { const champions = { pontosCorridos: {}, mataMata: {}, artilheiro: {}, luvaDeOuro: {}, melhorDoMes: {} }; const ligas = await mongoose.model('Liga').find({}).lean(); for (const liga of ligas) { // Pontos Corridos - 1º lugar const pcWinner = await mongoose.model('PontosCorridos') .findOne({ liga_id: liga._id, temporada: season }) .sort({ pontos: -1 }) .populate('participante_id', 'nome') .lean(); if (pcWinner) { champions.pontosCorridos[liga.nome] = { participante: pcWinner.participante_id?.nome, pontos: pcWinner.pontos }; } // Mata-Mata - Campeão const mmChampion = await mongoose.model('MataMata') .findOne({ liga_id: liga._id, temporada: season, fase: 'final', vencedor: true }) .populate('participante_id', 'nome') .lean(); if (mmChampion) { champions.mataMata[liga.nome] = { participante: mmChampion.participante_id?.nome }; } // Artilheiro - Maior pontuação única const artilheiro = await mongoose.model('Rodada') .findOne({ liga_id: liga._id, temporada: season }) .sort({ pontos_rodada: -1 }) .populate('participante_id', 'nome') .lean(); if (artilheiro) { champions.artilheiro[liga.nome] = { participante: artilheiro.participante_id?.nome, pontos: artilheiro.pontos_rodada, rodada: artilheiro.rodada_num }; } } return champions; } // ==================== LIMPEZA SELETIVA ==================== async function cleanupSeasonData(season) { console.log(`🧹 Limpando dados da temporada ${season}...`); // Collections a limpar (dados de jogo) const collectionsToClean = [ 'rodadas', 'rankings', 'pontos_corridos', 'mata_mata', 'top10', 'financeiro_cache' ]; for (const collName of collectionsToClean) { try { const result = await mongoose.connection.db .collection(collName) .deleteMany({ temporada: season }); console.log(` ✅ ${collName}: ${result.deletedCount} docs removidos`); } catch (error) { console.error(` 🔴 Erro ao limpar ${collName}:`, error.message); } } console.log('✅ Limpeza concluída'); } // ==================== PRESERVAÇÃO ==================== async function updateUsersRegistry() { console.log('📝 Atualizando users_registry.json...'); const users = await mongoose.model('User').find({}).lean(); const registry = users.map(user => ({ _id: user._id, email: user.email, nome: user.nome, active_seasons: user.active_seasons, created_at: user.created_at, last_updated: new Date() })); fs.writeFileSync( 'data/users_registry.json', JSON.stringify(registry, null, 2) ); console.log(`✅ Registry atualizado (${users.length} usuários)`); } // ==================== PREPARAÇÃO NOVA TEMPORADA ==================== async function prepareNewSeason(season) { console.log(`🚀 Preparando temporada ${season}...`); // 1. Criar configuração da nova temporada const config = { temporada: season, rodada_atual: 1, ativa: true, data_inicio: new Date(`${season}-03-01`), data_fim: new Date(`${season}-12-31`) }; await mongoose.connection.db .collection('system_config') .updateOne( { tipo: 'temporada_atual' }, { $set: config }, { upsert: true } ); // 2. Resetar saldos dos participantes await mongoose.model('Participante').updateMany( {}, { $set: { saldo_temporada: 0, pontos_acumulados: 0 } } ); console.log(`✅ Temporada ${season} preparada`); } // ==================== VALIDAÇÕES ==================== async function validateTurnover() { console.log('🔍 Executando validações...'); const validations = []; // 1. Verificar se temporada atual ainda tem dados const currentData = await mongoose.model('Rodada') .countDocuments({ temporada: CURRENT_SEASON }); if (currentData === 0) { validations.push('⚠️ Nenhuma rodada encontrada para temporada atual'); } else { validations.push(`✅ ${currentData} rodadas na temporada ${CURRENT_SEASON}`); } // 2. Verificar se já existe snapshot const snapshotPath = `data/history/${CURRENT_SEASON}`; if (fs.existsSync(snapshotPath)) { validations.push('⚠️ Snapshot já existe - será sobrescrito'); } // 3. Verificar espaço em disco const diskUsage = await checkDiskSpace(); if (diskUsage > 90) { validations.push(`🔴 Espaço em disco crítico: ${diskUsage}%`); } else { validations.push(`✅ Espaço em disco OK: ${diskUsage}%`); } validations.forEach(v => console.log(` ${v}`)); return validations; } function checkDiskSpace() { // Implementação simplificada return 50; // Placeholder } // ==================== EXECUÇÃO PRINCIPAL ==================== async function main() { console.log('╔════════════════════════════════════════════╗'); console.log('║ VIRADA DE TEMPORADA - SUPER CARTOLA ║'); console.log(`║ ${CURRENT_SEASON} → ${NEW_SEASON} ║`); console.log('╚════════════════════════════════════════════╝'); console.log(''); const isDryRun = process.argv.includes('--dry-run'); if (isDryRun) { console.log('🔵 MODO DRY-RUN (simulação)'); console.log(''); } try { await connectDB(); // 1. Validações await validateTurnover(); console.log(''); // 2. Confirmação (se não for dry-run) if (!isDryRun) { console.log('⚠️ ATENÇÃO: Esta operação irá:'); console.log(' - Criar snapshot da temporada atual'); console.log(' - Limpar dados de jogo (rodadas, rankings, etc)'); console.log(' - Preparar nova temporada'); console.log(''); console.log('Para confirmar, adicione --confirm ao comando'); if (!process.argv.includes('--confirm')) { console.log(''); console.log('Comando cancelado. Use:'); console.log(' node scripts/turn_key_2026.js --confirm'); process.exit(0); } } // 3. Snapshot final if (!isDryRun) { await createSeasonSnapshot(CURRENT_SEASON); } else { console.log('📸 [DRY-RUN] Criaria snapshot de', CURRENT_SEASON); } // 4. Atualizar registry if (!isDryRun) { await updateUsersRegistry(); } else { console.log('📝 [DRY-RUN] Atualizaria users_registry.json'); } // 5. Limpeza if (!isDryRun) { await cleanupSeasonData(CURRENT_SEASON); } else { console.log('🧹 [DRY-RUN] Limparia dados de', CURRENT_SEASON); } // 6. Preparar nova temporada if (!isDryRun) { await prepareNewSeason(NEW_SEASON); } else { console.log('🚀 [DRY-RUN] Prepararia temporada', NEW_SEASON); } console.log(''); console.log('═══════════════════════════════════════════'); console.log('✅ VIRADA DE TEMPORADA CONCLUÍDA COM SUCESSO'); console.log('═══════════════════════════════════════════'); } catch (error) { console.error(''); console.error('🔴 ERRO DURANTE VIRADA DE TEMPORADA:', error); console.error(''); console.error('Stack:', error.stack); process.exit(1); } finally { await mongoose.disconnect(); console.log(''); console.log('Desconectado do MongoDB'); } } // Executar if (require.main === module) { main(); } module.exports = { createSeasonSnapshot, cleanupSeasonData };
Uso:
# Testar (dry-run) node scripts/turn_key_2026.js --dry-run # Executar de verdade (após 01/01/2026) node scripts/turn_key_2026.js --confirm
3. 👥 Gestão de Acesso (User Management)
3.1 Schema de Active Seasons
// Model: User const userSchema = new mongoose.Schema({ email: { type: String, required: true, unique: true }, password: { type: String, required: true }, nome: String, active_seasons: { type: [String], default: [] }, // Ex: ["2025", "2026"] created_at: { type: Date, default: Date.now }, last_login: Date }); // Middleware de autenticação function checkSeasonAccess(req, res, next) { const { temporada } = req.params; const user = req.session.user; if (!user.active_seasons.includes(temporada)) { return res.status(403).json({ error: 'Acesso negado', message: 'Usuário não tem acesso a esta temporada', active_seasons: user.active_seasons }); } next(); }
3.2 Script de Renovação de Usuários
Criar
/scripts/admin_renew_user.js:
const mongoose = require('mongoose'); const User = require('../models/User'); async function listPendingRenewals(season) { const users = await User.find({ active_seasons: { $ne: season } }).select('email nome active_seasons'); console.log(`📋 Usuários pendentes de renovação para ${season}:`); console.log(''); users.forEach((user, index) => { console.log(`${index + 1}. ${user.email} (${user.nome})`); console.log(` Temporadas ativas: ${user.active_seasons.join(', ')}`); }); console.log(''); console.log(`Total: ${users.length} usuários`); } async function renewUser(userId, season) { const user = await User.findById(userId); if (!user) { throw new Error('Usuário não encontrado'); } if (user.active_seasons.includes(season)) { console.log(`⚠️ Usuário ${user.email} já tem acesso a ${season}`); return; } user.active_seasons.push(season); await user.save(); console.log(`✅ Renovado: ${user.email} → temporadas: ${user.active_seasons.join(', ')}`); // Audit log await createAuditLog({ action: 'USER_RENEWAL', target: user._id, details: { season, by: 'admin_script' } }); } async function revokeAccess(userId, season) { const user = await User.findById(userId); if (!user) { throw new Error('Usuário não encontrado'); } user.active_seasons = user.active_seasons.filter(s => s !== season); await user.save(); console.log(`🔴 Revogado: ${user.email} → temporadas: ${user.active_seasons.join(', ')}`); // Audit log await createAuditLog({ action: 'USER_REVOCATION', target: user._id, details: { season, by: 'admin_script' } }); } async function getStats(season) { const total = await User.countDocuments(); const active = await User.countDocuments({ active_seasons: season }); const pending = total - active; console.log('📊 Estatísticas de Renovação'); console.log(''); console.log(`Temporada: ${season}`); console.log(`Total de usuários: ${total}`); console.log(`Com acesso: ${active} (${((active/total)*100).toFixed(1)}%)`); console.log(`Pendentes: ${pending} (${((pending/total)*100).toFixed(1)}%)`); } // CLI const args = process.argv.slice(2); const command = args[0]; const season = process.env.CURRENT_SEASON || '2026'; async function main() { await mongoose.connect(process.env.MONGO_URI); try { switch(command) { case '--list-pending': await listPendingRenewals(season); break; case '--user': const userId = args[1]; if (!userId) { console.error('Erro: userId não fornecido'); process.exit(1); } if (args.includes('--revoke')) { await revokeAccess(userId, season); } else { await renewUser(userId, season); } break; case '--stats': await getStats(season); break; default: console.log('Uso:'); console.log(' node scripts/admin_renew_user.js --list-pending'); console.log(' node scripts/admin_renew_user.js --user <userId>'); console.log(' node scripts/admin_renew_user.js --user <userId> --revoke'); console.log(' node scripts/admin_renew_user.js --stats'); } } finally { await mongoose.disconnect(); } } main();
4. 📊 Monitoramento e Diagnóstico
4.1 Script de Health Check do DB
Criar
/scripts/db_health_check.js:
async function checkDBHealth() { const health = { timestamp: new Date(), status: 'unknown', checks: {} }; try { // 1. Conexão const startTime = Date.now(); await mongoose.connection.db.admin().ping(); health.checks.connection = { status: 'ok', latency: Date.now() - startTime }; // 2. Collections const collections = await mongoose.connection.db.listCollections().toArray(); health.checks.collections = { status: 'ok', count: collections.length, names: collections.map(c => c.name) }; // 3. Índices const indexStats = await checkIndexes(); health.checks.indexes = indexStats; // 4. Tamanho do DB const stats = await mongoose.connection.db.stats(); health.checks.size = { status: 'ok', dataSize: (stats.dataSize / 1024 / 1024).toFixed(2) + ' MB', storageSize: (stats.storageSize / 1024 / 1024).toFixed(2) + ' MB', indexes: (stats.indexSize / 1024 / 1024).toFixed(2) + ' MB' }; // 5. Queries lentas const slowQueries = await checkSlowQueries(); health.checks.performance = slowQueries; // Status geral const hasErrors = Object.values(health.checks).some(c => c.status === 'error'); health.status = hasErrors ? 'degraded' : 'healthy'; } catch (error) { health.status = 'error'; health.error = error.message; } return health; } async function checkIndexes() { const collections = ['participantes', 'rodadas', 'acertos_financeiros']; const indexReport = {}; for (const collName of collections) { const indexes = await mongoose.connection.db .collection(collName) .indexes(); indexReport[collName] = { count: indexes.length, hasLigaId: indexes.some(idx => idx.key.liga_id), details: indexes.map(idx => ({ name: idx.name, keys: Object.keys(idx.key) })) }; } return { status: 'ok', collections: indexReport }; } async function checkSlowQueries() { // Verificar system.profile se ativado const profiling = await mongoose.connection.db.command({ profile: -1 }); return { status: profiling.was === 0 ? 'disabled' : 'enabled', level: profiling.was }; }
4.2 Métricas Críticas
// Monitorar estas métricas continuamente const CRITICAL_METRICS = { // Performance avgQueryTime: { threshold: 100, unit: 'ms' }, // <100ms connectionPoolSize: { threshold: 50, unit: 'connections' }, // Data Integrity orphanedDocuments: { threshold: 0, unit: 'docs' }, // 0 duplicateIds: { threshold: 0, unit: 'docs' }, // 0 // Multi-tenant queriesWithoutLigaId: { threshold: 5, unit: 'queries/hour' }, // Storage dataGrowthRate: { threshold: 10, unit: '%/month' }, indexFragmentation: { threshold: 30, unit: '%' } };
5. 🔧 Migrations e Alterações de Schema
5.1 Pattern de Migration Segura
// /migrations/YYYY-MM-DD_description.js const mongoose = require('mongoose'); async function up() { console.log('🔼 Executando migration: description'); // 1. Backup antes de qualquer coisa const backup = await backupCollection('collection_name'); try { // 2. Validar estado atual const count = await validatePreConditions(); console.log(`Documentos a migrar: ${count}`); // 3. Executar em batches (não travar o DB) const batchSize = 100; let processed = 0; while (processed < count) { const batch = await mongoose.connection.db .collection('collection_name') .find({}) .skip(processed) .limit(batchSize) .toArray(); for (const doc of batch) { // Transformação await transformDocument(doc); } processed += batchSize; console.log(`Progresso: ${processed}/${count}`); } // 4. Validar resultado await validatePostConditions(); console.log('✅ Migration concluída'); } catch (error) { console.error('🔴 Migration falhou:', error); console.error('Restaurando backup...'); await restoreFromBackup(backup); throw error; } } async function down() { console.log('🔽 Revertendo migration: description'); // Implementar rollback } module.exports = { up, down };
5.2 Migrations Comuns - Super Cartola
Adicionar campo liga_id a collection existente
async function addLigaIdToCollection(collectionName, defaultLigaId) { const result = await mongoose.connection.db .collection(collectionName) .updateMany( { liga_id: { $exists: false } }, { $set: { liga_id: defaultLigaId } } ); console.log(`Adicionado liga_id a ${result.modifiedCount} documentos`); // Criar índice await mongoose.connection.db .collection(collectionName) .createIndex({ liga_id: 1 }); }
Normalizar tipo de campo
async function normalizeFieldType(collectionName, fieldName, transformer) { const docs = await mongoose.connection.db .collection(collectionName) .find({ [fieldName]: { $exists: true } }) .toArray(); for (const doc of docs) { const newValue = transformer(doc[fieldName]); await mongoose.connection.db .collection(collectionName) .updateOne( { _id: doc._id }, { $set: { [fieldName]: newValue } } ); } } // Exemplo: String → ObjectId await normalizeFieldType( 'rodadas', 'liga_id', (value) => mongoose.Types.ObjectId(value) );
6. 🗂️ Índices e Otimização
6.1 Índices Obrigatórios - Super Cartola
// Executar em cada collection const REQUIRED_INDEXES = { participantes: [ { liga_id: 1, temporada: 1 }, { liga_id: 1, email: 1 }, { user_id: 1 } ], rodadas: [ { liga_id: 1, temporada: 1, rodada_num: 1 }, { participante_id: 1, temporada: 1 }, { liga_id: 1, temporada: 1, pontos_rodada: -1 } // Para ranking ], acertos_financeiros: [ { liga_id: 1, temporada: 1 }, { participante_id: 1, temporada: 1 }, { idempotency_key: 1 } // Unique ], pontos_corridos: [ { liga_id: 1, temporada: 1, pontos: -1 } ] }; async function ensureIndexes() { for (const [collName, indexes] of Object.entries(REQUIRED_INDEXES)) { console.log(`Verificando índices em ${collName}...`); for (const index of indexes) { const indexName = Object.keys(index).join('_'); try { await mongoose.connection.db .collection(collName) .createIndex(index, { name: indexName, background: true }); console.log(` ✅ ${indexName}`); } catch (error) { if (error.code === 85) { console.log(` ⚠️ ${indexName} já existe com definição diferente`); } else { console.error(` 🔴 Erro ao criar ${indexName}:`, error.message); } } } } }
6.2 Análise de Queries
// Habilitar profiling temporariamente async function analyzeQueries(durationMinutes = 10) { // 1. Ativar profiling await mongoose.connection.db.setProfilingLevel(1, { slowms: 100 }); console.log(`Profiling ativado por ${durationMinutes} minutos`); // 2. Aguardar await new Promise(resolve => setTimeout(resolve, durationMinutes * 60 * 1000)); // 3. Analisar const slowQueries = await mongoose.connection.db .collection('system.profile') .find({ millis: { $gt: 100 } }) .sort({ millis: -1 }) .limit(20) .toArray(); console.log('Top 20 queries mais lentas:'); slowQueries.forEach(q => { console.log(` ${q.millis}ms - ${q.ns} - ${JSON.stringify(q.command)}`); }); // 4. Desativar profiling await mongoose.connection.db.setProfilingLevel(0); }
7. 📋 Comandos Úteis
7.1 Quick Reference
# === VIRADA DE TEMPORADA === node scripts/turn_key_2026.js --dry-run # Testar node scripts/turn_key_2026.js --confirm # Executar # === GESTÃO DE USUÁRIOS === node scripts/admin_renew_user.js --list-pending node scripts/admin_renew_user.js --user <id> node scripts/admin_renew_user.js --stats # === HEALTH CHECK === node scripts/db_health_check.js # === BACKUP MANUAL === mongodump --uri="$MONGO_URI" --out=backups/$(date +%Y%m%d) # === RESTORE === mongorestore --uri="$MONGO_URI" backups/YYYYMMDD # === ANÁLISE === mongo --eval "db.stats()" # Estatísticas do DB mongo --eval "db.participantes.getIndexes()" # Ver índices mongo --eval "db.participantes.stats()" # Stats de collection
7.2 MongoDB Shell Snippets
// Contar documentos por temporada db.rodadas.aggregate([ { $group: { _id: "$temporada", count: { $sum: 1 } } }, { $sort: { _id: 1 } } ]); // Encontrar documentos órfãos (sem liga_id) db.participantes.find({ liga_id: { $exists: false } }).count(); // Verificar duplicatas db.participantes.aggregate([ { $group: { _id: { liga_id: "$liga_id", email: "$email" }, count: { $sum: 1 } }}, { $match: { count: { $gt: 1 } } } ]); // Top 10 maiores collections db.getCollectionNames().map(c => ({ name: c, size: db[c].stats().size })).sort((a,b) => b.size - a.size).slice(0, 10);
8. 🚨 Troubleshooting
8.1 Problemas Comuns
| Problema | Causa Provável | Solução |
|---|---|---|
| Queries lentas | Sem índice | |
| Documentos órfãos | Migration incompleta | Rodar script de cleanup |
| Espaço em disco | Dados antigos acumulados | Virada de temporada |
| Conexões esgotadas | Pool pequeno | Aumentar connectionPoolSize |
| Lock timeout | Operação muito grande | Dividir em batches |
8.2 Recovery Procedures
// Recuperar de backup específico async function recoverFromBackup(backupPath) { console.log(`Recuperando de ${backupPath}...`); const backup = JSON.parse(fs.readFileSync(backupPath, 'utf8')); // Validar backup if (!backup.collection || !backup.data) { throw new Error('Backup inválido'); } // Limpar collection atual (com confirmação!) console.log(`⚠️ Isso irá DELETAR todos os dados de ${backup.collection}`); console.log('Digite "CONFIRMAR" para continuar:'); // ... aguardar confirmação // Restaurar await mongoose.connection.db .collection(backup.collection) .deleteMany({}); await mongoose.connection.db .collection(backup.collection) .insertMany(backup.data); console.log(`✅ Recuperado ${backup.count} documentos`); }
9. 📚 Best Practices
9.1 Naming Conventions
// Collections: plural, snake_case 'participantes', 'acertos_financeiros', 'rodadas' // Campos: snake_case 'liga_id', 'participante_id', 'rodada_num' // Índices: descritivo 'liga_temporada_idx', 'participante_email_unique' // Backups: timestamp + descrição '2026-01-15_pre-migration-participantes.json'
9.2 Data Integrity Checks
// Executar periodicamente async function runIntegrityChecks() { const checks = []; // 1. Órfãos (participantes sem liga) const orphans = await mongoose.model('Participante').countDocuments({ liga_id: { $exists: false } }); checks.push({ name: 'Órfãos', count: orphans, critical: orphans > 0 }); // 2. Duplicatas const duplicates = await findDuplicates('participantes', ['liga_id', 'email']); checks.push({ name: 'Duplicatas', count: duplicates.length, critical: duplicates.length > 0 }); // 3. Referências quebradas const brokenRefs = await checkReferences(); checks.push({ name: 'Refs quebradas', count: brokenRefs, critical: brokenRefs > 0 }); // Report console.log('🔍 Integrity Checks:'); checks.forEach(c => { const icon = c.critical ? '🔴' : '✅'; console.log(` ${icon} ${c.name}: ${c.count}`); }); return checks; }
STATUS: 🛡️ DB Guardian - ATIVO & VIGILANTE
Versão: 2.0 (Super Cartola Master Edition)
Última atualização: 2026-01-17