UNPKG

api-stats-logger

Version:

SDK completo de logging e monitoramento de APIs com nova estrutura de logs organizada, auto-instrumentação, dashboard em tempo real e CLI para configuração automática. Suporta logs estruturados por contexto (HTTP, business, security, system) com campos op

1,513 lines (1,251 loc) 45.3 kB
#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const axios = require('axios'); const UIComponents = require('./ui-components'); class ModernApiStatsCLI { constructor() { this.ui = new UIComponents(); this.baseUrl = process.env.API_STATS_URL || 'https://apistats.eway-api.com.br'; this.authToken = null; this.deviceId = null; this.user = null; this.initialized = false; // Inicializar armazenamento seguro const SecureStorage = require('./secure-storage'); this.secureStorage = new SecureStorage(); } // Inicialização assíncrona separada async initialize() { if (this.initialized) return; try { // Migrar credenciais antigas primeiro await this.secureStorage.migrateOldCredentials(); // Carregar dados de autenticação await this.loadAuthData(); this.initialized = true; } catch (error) { console.warn('⚠️ Erro na inicialização:', error.message); this.initialized = true; // Continuar mesmo com erro } } // Obter headers padrão para requisições autenticadas getAuthHeaders() { const headers = { 'Content-Type': 'application/json', 'User-Agent': 'API-Stats-CLI-Modern/2.2.6' }; if (this.authToken) { headers['Authorization'] = `Bearer ${this.authToken}`; } if (this.deviceId) { headers['x-device-id'] = this.deviceId; } return headers; } async run() { // Inicializar primeiro await this.initialize(); // Verificar se tem argumentos de comando if (process.argv.length > 2) { // Para comandos via linha de comando, usar interface simplificada this.ui.info('Use a interface principal para comandos específicos.'); this.ui.info('Execute: npx api-stats-logger'); return; } // Interface principal this.ui.header(); if (this.authToken) { await this.showAuthenticatedInterface(); } else { await this.showLoginInterface(); } } async showLoginInterface() { this.ui.section('Autenticação Necessária', 'Faça login para começar a usar o API Stats Logger'); const choices = [ { name: `${this.ui.icons.google} Login com Google (recomendado)`, value: 'google', short: 'Google OAuth' }, { name: `${this.ui.icons.user} Login tradicional (email/senha)`, value: 'traditional', short: 'Login tradicional' }, { name: `${this.ui.icons.key} Usar API key existente`, value: 'apikey', short: 'API Key' } ]; const { selected } = await this.ui.select('Escolha uma opção de autenticação:', choices); switch (selected) { case 'google': await this.googleLogin(); break; case 'traditional': await this.traditionalLogin(); break; case 'apikey': await this.apiKeyLogin(); break; } } async showAuthenticatedInterface() { // Carregar dados do usuário await this.loadUserData(); if (this.user) { this.ui.userStatus(this.user); } // Verificar status da conexão const connectionStatus = await this.checkConnectionStatus(); if (!connectionStatus.online) { this.ui.warning('⚠️ Modo offline - algumas funcionalidades podem estar limitadas'); } // Verificar se tem projeto selecionado const currentProject = this.getCurrentProject(); if (currentProject) { this.ui.infoBox('Projeto Atual', `${currentProject.name} (${currentProject.environment})`); } const choices = [ { name: `${this.ui.icons.project} Gerenciar Projetos`, value: 'projects', short: 'Projetos' }, { name: `${this.ui.icons.arrow} Selecionar Projeto`, value: 'select', short: 'Selecionar' }, { name: `${this.ui.icons.project} Criar Novo Projeto`, value: 'create', short: 'Criar' }, { name: `${this.ui.icons.settings} Configurações`, value: 'settings', short: 'Configurações' }, { name: `${this.ui.icons.link} Abrir Dashboard`, value: 'dashboard', short: 'Dashboard' }, { name: `${this.ui.icons.help} Ajuda`, value: 'help', short: 'Ajuda' }, { name: `${this.ui.icons.logout} Logout`, value: 'logout', short: 'Logout' } ]; const { selected } = await this.ui.select('O que você deseja fazer?', choices); switch (selected) { case 'projects': await this.manageProjects(); break; case 'select': await this.selectProject(); break; case 'create': await this.createProject(); break; case 'settings': await this.openSettings(); break; case 'dashboard': await this.openDashboard(); break; case 'help': await this.showHelp(); break; case 'logout': await this.logout(); break; } } async googleLogin() { this.ui.section('Autenticação Google OAuth', 'Conecte-se com sua conta Google'); try { // Verificar se o backend está disponível const healthCheck = await this.ui.withProgress('Verificando suporte ao Google OAuth', async () => { try { const response = await axios.get(`${this.baseUrl}/health`, { timeout: 5000 }); return response.status === 200; } catch (error) { return false; } }); if (!healthCheck) { this.ui.error('Não foi possível conectar ao servidor.'); await this.traditionalLogin(); return; } // Iniciar servidor local temporário para capturar callback const serverPort = await this.startLocalServer(); this.ui.info('Abrindo autenticação Google no navegador...'); this.ui.warning('Complete a autenticação no navegador - o retorno será automático.'); // Abrir URL de autenticação Google com parâmetro da porta da CLI const authUrl = `${this.baseUrl}/auth/google?port=${serverPort}`; const open = await import('open'); await open.default(authUrl); // Aguardar resultado do servidor local const result = await this.waitForAuthResult(); // Parar servidor local this.stopLocalServer(); if (!result.success) { throw new Error(result.error || 'Autenticação falhou'); } this.authToken = result.data.access_token; this.deviceId = result.data.deviceId; await this.storeAuthData(); this.ui.success('Autenticação Google realizada com sucesso!'); this.ui.info(`Bem-vindo, ${result.data.user.name || result.data.user.email}!`); this.ui.space(); await this.showProjectSelection(); } catch (error) { // Garantir que o servidor local seja parado em caso de erro this.stopLocalServer(); this.ui.error('Erro na autenticação Google: ' + error.message); if (error.message.includes('Conta não encontrada')) { this.ui.warning('Você precisa ter uma conta no sistema antes de usar a CLI.'); this.ui.info('Acesse o dashboard web primeiro para criar sua conta.'); } await this.traditionalLogin(); } } async startLocalServer() { const http = require('http'); // Encontrar porta disponível const getAvailablePort = () => { return new Promise((resolve, reject) => { const server = require('net').createServer(); server.listen(0, (err) => { if (err) reject(err); const port = server.address().port; server.close(() => resolve(port)); }); }); }; const port = await getAvailablePort(); this.localServer = http.createServer((req, res) => { const url = new URL(req.url, `http://localhost:${port}`); if (url.pathname === '/auth/success') { const data = url.searchParams.get('data'); if (data) { try { this.authResult = { success: true, data: JSON.parse(decodeURIComponent(data)) }; } catch (error) { this.authResult = { success: false, error: 'Dados de autenticação inválidos' }; } } else { this.authResult = { success: false, error: 'Dados de autenticação não recebidos' }; } // Resposta de sucesso para o navegador res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(` <!DOCTYPE html> <html> <head> <title>API Stats Logger - Autenticação</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; padding: 40px; background: #f5f5f5; } .container { max-width: 400px; margin: 0 auto; background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); text-align: center; } .success { color: #22c55e; font-size: 24px; margin-bottom: 20px; } .info { color: #6b7280; margin-bottom: 20px; } .logo { font-size: 18px; font-weight: bold; color: #1f2937; margin-bottom: 30px; } </style> </head> <body> <div class="container"> <div class="logo">🚀 API Stats Logger</div> <div class="success">✅ Autenticação realizada com sucesso!</div> <div class="info">Você pode fechar esta janela e voltar para o terminal.</div> </div> </body> </html> `); } else if (url.pathname === '/auth/error') { const error = url.searchParams.get('error') || 'Erro desconhecido'; this.authResult = { success: false, error: decodeURIComponent(error) }; // Resposta de erro para o navegador res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(` <!DOCTYPE html> <html> <head> <title>API Stats Logger - Erro de Autenticação</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; margin: 0; padding: 40px; background: #f5f5f5; } .container { max-width: 400px; margin: 0 auto; background: white; padding: 40px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); text-align: center; } .error { color: #ef4444; font-size: 24px; margin-bottom: 20px; } .info { color: #6b7280; margin-bottom: 20px; } .logo { font-size: 18px; font-weight: bold; color: #1f2937; margin-bottom: 30px; } </style> </head> <body> <div class="container"> <div class="logo">🚀 API Stats Logger</div> <div class="error">❌ Erro na autenticação</div> <div class="info">${error}</div> <div class="info">Você pode fechar esta janela e voltar para o terminal.</div> </div> </body> </html> `); } else { res.writeHead(404); res.end('Not Found'); } }); this.localServer.listen(port); return port; } stopLocalServer() { if (this.localServer) { this.localServer.close(); this.localServer = null; } } async waitForAuthResult() { return new Promise((resolve) => { const checkResult = () => { if (this.authResult) { const result = this.authResult; this.authResult = null; resolve(result); } else { setTimeout(checkResult, 500); } }; // Timeout de 5 minutos setTimeout(() => { if (!this.authResult) { resolve({ success: false, error: 'Timeout: autenticação não foi completada em 5 minutos' }); } }, 300000); checkResult(); }); } async traditionalLogin() { this.ui.section('Login Tradicional', 'Entre com suas credenciais'); const { value: username } = await this.ui.input('Email ou nome de usuário:', { validate: (input) => input.length > 0 || 'Campo obrigatório' }); const { value: password } = await this.ui.password('Senha:', { validate: (input) => input.length > 0 || 'Campo obrigatório' }); try { const result = await this.ui.withProgress('Fazendo login', async () => { const response = await axios.post(`${this.baseUrl}/auth/login`, { username, password }, { headers: { 'Content-Type': 'application/json', 'User-Agent': 'API-Stats-CLI-Modern/2.2.5' } }); return response.data; }); this.authToken = result.access_token; this.deviceId = result.deviceId; await this.storeAuthData(); this.ui.success('Login realizado com sucesso!'); this.ui.space(); await this.showProjectSelection(); } catch (error) { this.ui.error('Erro no login: ' + (error.response?.data?.message || error.message)); await this.showLoginInterface(); } } async register() { this.ui.section('Criar Nova Conta', 'Preencha os dados para criar sua conta'); const { value: username } = await this.ui.input('Nome de usuário:', { validate: (input) => input.length >= 3 || 'Mínimo 3 caracteres' }); const { value: email } = await this.ui.input('Email:', { validate: (input) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input) || 'Email inválido' }); const { value: password } = await this.ui.password('Senha:', { validate: (input) => input.length >= 6 || 'Mínimo 6 caracteres' }); const { value: confirmPassword } = await this.ui.password('Confirmar senha:', { validate: (input) => input === password || 'Senhas não coincidem' }); try { await this.ui.withProgress('Criando conta', async () => { await axios.post(`${this.baseUrl}/auth/register`, { username, email, password, confirmPassword }, { headers: { 'Content-Type': 'application/json', 'User-Agent': 'API-Stats-CLI-Modern/2.1.3' } }); }); this.ui.success('Conta criada com sucesso!'); this.ui.info('Agora você pode fazer login com suas credenciais.'); this.ui.space(); await this.traditionalLogin(); } catch (error) { this.ui.error('Erro ao criar conta: ' + (error.response?.data?.message || error.message)); await this.showLoginInterface(); } } async apiKeyLogin() { this.ui.section('Autenticação por API Key', 'Use uma API key existente'); const { value: apiKey } = await this.ui.input('API Key:', { validate: (input) => input.length > 0 || 'API Key obrigatória' }); const { value: service } = await this.ui.input('Nome do serviço/projeto:', { validate: (input) => input.length > 0 || 'Nome do serviço é obrigatório' }); const { value: environment } = await this.ui.input('Ambiente [development/staging/production]:', { default: 'development' }); // Configurar projeto com API key existente const config = { apiKey, service, environment, existingKey: true }; await this.ui.withProgress('Configurando projeto', async () => { await this.setupProjectWithConfig(config); }); this.ui.success('API Key configurada com sucesso!'); this.ui.success('Arquivos de configuração gerados!'); this.ui.space(); this.ui.info('📖 Próximos passos:'); this.ui.info('1. Copie as variáveis do .env.api-stats para seu .env'); this.ui.info('2. Execute o exemplo gerado para testar'); this.ui.info('3. Acesse o dashboard para visualizar os logs'); // Sair da CLI após configuração process.exit(0); } async showProjectSelection() { this.ui.section('Seleção de Projeto', 'Escolha um projeto para trabalhar'); try { let projects = []; // Tentar carregar do cache primeiro const cachedProjects = await this.secureStorage.getProjectsCache(); if (cachedProjects && cachedProjects.length > 0) { projects = cachedProjects; this.ui.info('📦 Usando projetos em cache (modo offline)'); } // Tentar atualizar do servidor try { const freshProjects = await this.ui.withProgress('Carregando projetos', async () => { const response = await axios.get(`${this.baseUrl}/projects`, { headers: this.getAuthHeaders(), timeout: 5000 }); return response.data.data || []; }); if (freshProjects.length > 0) { projects = freshProjects; // Atualizar cache await this.secureStorage.setProjectsCache(projects); } } catch (error) { if (projects.length === 0) { // Se não tem cache e falhou a requisição, mostrar erro throw error; } else { // Se tem cache, continuar com dados offline this.ui.warning('⚠️ Não foi possível atualizar projetos, usando dados offline'); } } if (projects.length === 0) { this.ui.warning('Você não possui projetos ainda.'); const { confirmed } = await this.ui.confirm('Deseja criar um novo projeto?', true); if (confirmed) { await this.createProject(); } else { await this.showAuthenticatedInterface(); } return; } // Opções de projeto com interface moderna const choices = projects.map(project => ({ name: `${this.ui.icons.project} ${project.name}`, value: project, short: project.name })); // Adicionar opção para criar novo projeto choices.push({ name: `${this.ui.icons.project} Criar novo projeto`, value: 'create', short: 'Criar novo' }); const { selected } = await this.ui.select('Selecione um projeto:', choices, { pageSize: 8 }); if (selected === 'create') { await this.createProject(); } else { this.saveSelectedProject(selected); this.ui.success(`Projeto "${selected.name}" selecionado!`); // Configurar ambiente do projeto await this.setupProjectEnvironment(selected); } } catch (error) { this.ui.error('Erro ao carregar projetos: ' + error.message); await this.showAuthenticatedInterface(); } } async setupProjectEnvironment(project) { this.ui.section('Configuração do Projeto', `Configurando ambiente para ${project.name}`); const choices = [ { name: 'Configurar automaticamente', value: 'auto' }, { name: 'Configurar manualmente', value: 'manual' }, { name: 'Pular configuração', value: 'skip' } ]; const { selected } = await this.ui.select('Como deseja configurar o projeto?', choices); switch (selected) { case 'auto': await this.autoSetupProject(project); break; case 'manual': await this.manualSetupProject(project); break; case 'skip': this.ui.info('Configuração pulada. Você pode configurar depois.'); break; } this.ui.success('Configuração concluída!'); this.ui.space(); await this.showAuthenticatedInterface(); } async autoSetupProject(project) { this.ui.info('Verificando API Key do projeto...'); // Verificar se o projeto tem API key let hasApiKey = false; let apiKey = null; try { // Tentar obter API key do projeto (simulado - na prática seria via API) // Para agora, vamos perguntar se o usuário tem API key const { confirmed } = await this.ui.confirm('Este projeto já possui uma API Key configurada?', false); if (confirmed) { const { value } = await this.ui.input('Cole a API Key do projeto:'); apiKey = value; hasApiKey = true; } else { const { confirmed: generateNew } = await this.ui.confirm('Deseja gerar uma nova API Key para este projeto?', true); if (generateNew) { // Gerar nova API key redirecionando para o site this.ui.info('Abrindo página para gerar nova API key...'); const open = require('open'); await open(`https://apistats.eway.app.br/applications/${project._id}/configuracoes`); const { value } = await this.ui.input('Cole a nova API Key gerada:'); apiKey = value; hasApiKey = true; } else { this.ui.warning('Configuração cancelada. API Key é necessária para integração.'); return; } } } catch (error) { this.ui.warning('Não foi possível verificar API Key do projeto automaticamente.'); const { value } = await this.ui.input('Cole a API Key do projeto:'); apiKey = value; hasApiKey = true; } if (hasApiKey && apiKey) { // Configurar projeto com API key const config = { apiKey, service: project.name, environment: project.environment || 'production', projectId: project._id, framework: this.detectFramework() }; await this.setupProjectWithConfig(config); this.ui.success('Configuração automática concluída!'); } else { this.ui.error('API Key necessária para continuar.'); } } async manualSetupProject(project) { this.ui.info('Configuração manual em desenvolvimento...'); this.ui.info('Use a configuração automática por enquanto.'); } async createProject() { this.ui.section('Criar Novo Projeto'); const { value: name } = await this.ui.input('Nome do projeto:', { validate: (input) => input.length > 0 || 'Nome é obrigatório' }); const { value: description } = await this.ui.input('Descrição (opcional):'); const environments = [ { name: 'Production', value: 'production' }, { name: 'Development', value: 'development' }, { name: 'Testing', value: 'testing' }, { name: 'Staging', value: 'staging' } ]; const { selected: environment } = await this.ui.select('Ambiente:', environments); try { const project = await this.ui.withProgress('Criando projeto', async () => { const response = await axios.post(`${this.baseUrl}/projects`, { name, description, environment }, { headers: this.getAuthHeaders() }); return response.data; }); this.ui.success(`Projeto "${project.name}" criado com sucesso!`); this.saveSelectedProject(project); // Configurar ambiente do projeto await this.setupProjectEnvironment(project); } catch (error) { this.ui.error('Erro ao criar projeto: ' + (error.response?.data?.message || error.message)); } } async manageProjects() { this.ui.section('Gerenciamento de Projetos'); const choices = [ { name: `${this.ui.icons.project} Listar Projetos`, value: 'list' }, { name: `${this.ui.icons.arrow} Selecionar Projeto`, value: 'select' }, { name: `${this.ui.icons.project} Criar Projeto`, value: 'create' }, { name: `${this.ui.icons.key} Gerar API Key`, value: 'apikey' }, { name: `${this.ui.icons.delete} Deletar Projeto`, value: 'delete' }, { name: `${this.ui.icons.arrow} Voltar`, value: 'back' } ]; const { selected } = await this.ui.select('Escolha uma opção:', choices); switch (selected) { case 'list': await this.listProjects(); break; case 'select': await this.selectProject(); break; case 'create': await this.createProject(); break; case 'apikey': await this.generateApiKey(); break; case 'delete': await this.deleteProject(); break; case 'back': await this.showAuthenticatedInterface(); break; } } async listProjects() { const projects = await this.ui.withProgress('Carregando projetos', async () => { const response = await axios.get(`${this.baseUrl}/projects`, { headers: this.getAuthHeaders() }); return response.data.data || []; }); if (projects.length === 0) { this.ui.info('Você não possui projetos. Use "create" para criar um novo.'); return; } this.ui.section('Seus Projetos'); const rows = projects.map(project => [ project.name, project.description || 'Sem descrição', project.environment || 'production', project.createdAt ? new Date(project.createdAt).toLocaleDateString() : 'N/A' ]); this.ui.table(['Nome', 'Descrição', 'Ambiente', 'Criado em'], rows); } async deleteProject() { const projects = await this.ui.withProgress('Carregando projetos', async () => { const response = await axios.get(`${this.baseUrl}/projects`, { headers: this.getAuthHeaders() }); return response.data.data || []; }); if (projects.length === 0) { this.ui.error('Você não possui projetos para deletar.'); return; } const choices = projects.map(p => ({ name: `${this.ui.icons.project} ${p.name} (${p.environment})`, value: p, short: p.name })); const { selected } = await this.ui.select('Selecione o projeto para deletar:', choices); const { confirmed } = await this.ui.confirm(`Tem certeza que deseja deletar o projeto "${selected.name}"?`); if (!confirmed) { this.ui.info('Operação cancelada.'); return; } await this.ui.withProgress('Deletando projeto', async () => { await axios.delete(`${this.baseUrl}/projects/${selected._id}`, { headers: this.getAuthHeaders() }); }); this.ui.success('Projeto deletado com sucesso!'); } async selectProject() { await this.showProjectSelection(); } async generateApiKey() { const projects = await this.ui.withProgress('Carregando projetos', async () => { const response = await axios.get(`${this.baseUrl}/projects`, { headers: this.getAuthHeaders() }); return response.data.data || []; }); if (projects.length === 0) { this.ui.error('Você não possui projetos. Crie um projeto primeiro.'); return; } const choices = projects.map(p => ({ name: `${this.ui.icons.project} ${p.name} (${p.environment})`, value: p, short: p.name })); const { selected } = await this.ui.select('Selecione o projeto:', choices); const url = `https://apistats.eway.app.br/applications/${selected._id}/configuracoes`; this.ui.info(`Abrindo página de configurações para gerar nova API key...`); this.ui.info(`URL: ${url}`); const open = require('open'); await open(url); } async openSettings() { await this.generateApiKey(); } async openDashboard() { const projects = await this.ui.withProgress('Carregando projetos', async () => { const response = await axios.get(`${this.baseUrl}/projects`, { headers: this.getAuthHeaders() }); return response.data.data || []; }); if (projects.length === 0) { this.ui.error('Você não possui projetos.'); return; } const choices = projects.map(p => ({ name: `${this.ui.icons.project} ${p.name} (${p.environment})`, value: p, short: p.name })); const { selected } = await this.ui.select('Selecione o projeto:', choices); const url = `https://apistats.eway.app.br/applications/${selected._id}`; this.ui.info(`Abrindo projeto no navegador...`); const open = require('open'); await open(url); } async showHelp() { const commands = [ { command: 'projects, p', description: 'Listar todos os projetos' }, { command: 'select, s', description: 'Selecionar projeto ativo' }, { command: 'create, c', description: 'Criar novo projeto' }, { command: 'delete, d [id]', description: 'Deletar projeto' }, { command: 'apikey, k [id]', description: 'Gerar nova API key' }, { command: 'open, o [id]', description: 'Abrir projeto no navegador' }, { command: 'settings [id]', description: 'Abrir configurações' }, { command: 'user, u', description: 'Informações do usuário' }, { command: 'logout', description: 'Fazer logout da CLI' }, { command: 'help', description: 'Mostrar esta ajuda' } ]; this.ui.commandList(commands); } async logout() { const { confirmed } = await this.ui.confirm('Tem certeza que deseja fazer logout?'); if (!confirmed) { this.ui.info('Operação cancelada.'); return; } await this.clearAuthData(); this.ui.success('Logout realizado com sucesso!'); this.ui.info('Use a CLI para fazer login novamente.'); // Reiniciar a interface await this.showLoginInterface(); } // Métodos de persistência segura melhorados async loadAuthData() { try { const credentials = await this.secureStorage.getCredentials(); if (credentials) { this.authToken = credentials.token; this.deviceId = credentials.deviceId; // Carregar dados do usuário (sem validação online na inicialização) await this.loadUserData(); } } catch (error) { console.warn('⚠️ Erro ao carregar credenciais:', error.message); } } async storeAuthData() { try { if (this.authToken) { const success = await this.secureStorage.setCredentials(this.authToken, this.deviceId); if (success) { // Salvar também dados do usuário para acesso offline if (this.user) { await this.secureStorage.setUserData(this.user); } this.ui.success('✅ Credenciais salvas com segurança'); } else { console.warn('⚠️ Não foi possível salvar credenciais de forma segura'); } } } catch (error) { console.warn('⚠️ Erro ao salvar credenciais:', error.message); } } async clearAuthData() { try { await this.secureStorage.clearCredentials(); this.authToken = null; this.deviceId = null; this.user = null; } catch (error) { console.warn('⚠️ Erro ao limpar credenciais:', error.message); } } // Validar credenciais armazenadas async validateStoredCredentials() { if (!this.authToken) return false; try { const response = await axios.get(`${this.baseUrl}/auth/profile`, { headers: this.getAuthHeaders(), timeout: 5000 }); return response.status === 200; } catch (error) { // Se retornar 401/403, as credenciais são inválidas if (error.response && [401, 403].includes(error.response.status)) { return false; } // Para outros erros (rede, timeout, etc), assumir que as credenciais ainda são válidas // Isso permite trabalhar offline return true; } } // Carregar dados do usuário async loadUserData() { try { // Tentar carregar do cache primeiro const cachedUser = await this.secureStorage.getUserData(); if (cachedUser) { this.user = cachedUser; } // Se temos token, tentar atualizar dados do servidor (sem bloquear a inicialização) if (this.authToken) { try { const response = await axios.get(`${this.baseUrl}/auth/profile`, { headers: this.getAuthHeaders(), timeout: 3000 }); if (response.status === 200) { this.user = response.data; // Atualizar cache await this.secureStorage.setUserData(this.user); } } catch (error) { // Ignorar erros de rede na inicialização } } } catch (error) { console.warn('⚠️ Erro ao carregar dados do usuário:', error.message); } } // Validar credenciais apenas quando necessário async ensureValidCredentials() { if (!this.authToken) { throw new Error('Não autenticado'); } try { const response = await axios.get(`${this.baseUrl}/auth/profile`, { headers: this.getAuthHeaders(), timeout: 5000 }); return response.status === 200; } catch (error) { if (error.response && [401, 403].includes(error.response.status)) { // Credenciais inválidas, limpar e forçar novo login await this.clearAuthData(); throw new Error('Credenciais expiradas, faça login novamente'); } // Para outros erros, assumir que as credenciais ainda são válidas return true; } } async storeApiKey(apiKey) { // Para API keys, podemos usar o mesmo sistema seguro try { await this.secureStorage.setCredentials(apiKey, null); } catch (error) { console.warn('⚠️ Erro ao salvar API key:', error.message); } } // Método auxiliar para fazer requisições autenticadas async makeAuthenticatedRequest(url, options = {}) { await this.ensureValidCredentials(); return axios({ url: `${this.baseUrl}${url}`, headers: this.getAuthHeaders(), timeout: 5000, ...options }); } // Verificar status da conexão async checkConnectionStatus() { try { const response = await axios.get(`${this.baseUrl}/health`, { timeout: 3000, headers: { 'User-Agent': 'API-Stats-CLI-Modern/2.3.7' } }); return { online: response.status === 200, latency: Date.now() - Date.now(), // Simplificado server: 'API Stats Server' }; } catch (error) { return { online: false, error: error.message, server: 'API Stats Server' }; } } // Método para trabalhar offline async enableOfflineMode() { this.ui.info('🔄 Habilitando modo offline...'); // Verificar se temos dados em cache suficientes const hasCredentials = await this.secureStorage.getCredentials(); const hasUserData = await this.secureStorage.getUserData(); const hasProjects = await this.secureStorage.getProjectsCache(); if (!hasCredentials) { this.ui.error('❌ Não é possível trabalhar offline sem credenciais salvas'); return false; } if (!hasUserData) { this.ui.warning('⚠️ Dados do usuário não disponíveis offline'); } if (!hasProjects) { this.ui.warning('⚠️ Lista de projetos não disponível offline'); } this.ui.success('✅ Modo offline habilitado'); this.ui.info('📦 Usando dados em cache local'); return true; } // Sincronizar dados quando voltar online async syncWhenOnline() { const status = await this.checkConnectionStatus(); if (status.online) { this.ui.info('🔄 Sincronizando dados...'); try { // Revalidar credenciais const isValid = await this.validateStoredCredentials(); if (!isValid) { this.ui.warning('⚠️ Credenciais inválidas, faça login novamente'); return false; } // Atualizar dados do usuário await this.loadUserData(); // Atualizar lista de projetos const response = await axios.get(`${this.baseUrl}/projects`, { headers: this.getAuthHeaders(), timeout: 5000 }); if (response.data.data) { await this.secureStorage.setProjectsCache(response.data.data); } this.ui.success('✅ Dados sincronizados com sucesso'); return true; } catch (error) { this.ui.warning('⚠️ Erro na sincronização: ' + error.message); return false; } } return false; } saveSelectedProject(project) { const projectFile = path.join(process.cwd(), '.api-stats-project'); fs.writeFileSync(projectFile, JSON.stringify(project, null, 2)); } getCurrentProject() { const projectFile = path.join(process.cwd(), '.api-stats-project'); if (fs.existsSync(projectFile)) { try { return JSON.parse(fs.readFileSync(projectFile, 'utf8')); } catch (error) { return null; } } return null; } async loadUserData() { if (!this.authToken) return; try { const response = await axios.get(`${this.baseUrl}/auth/profile`, { headers: this.getAuthHeaders() }); this.user = { name: response.data.firstName ? `${response.data.firstName} ${response.data.lastName}` : response.data.username, email: response.data.email, projects: response.data.projects || 0 }; } catch (error) { // Token inválido, limpar dados await this.clearAuthData(); } } // Funções de configuração de projeto async setupProjectWithConfig(config) { const fs = require('fs'); const path = require('path'); // 1. Criar arquivo .env await this.createEnvFile(config); // 2. Criar exemplo de integração baseado no framework await this.createIntegrationExample(config); // 3. Criar arquivo de configuração await this.createConfigFile(config); // 4. Criar README com instruções await this.createSetupInstructions(config); } async createEnvFile(config) { const fs = require('fs'); const path = require('path'); const envContent = `# API Stats Logger Configuration API_STATS_ENABLED=true API_STATS_API_KEY=${config.apiKey} API_STATS_URL=${this.baseUrl}/logs API_STATS_SERVICE=${config.service} API_STATS_ENVIRONMENT=${config.environment} API_STATS_BATCH_SIZE=10 API_STATS_FLUSH_INTERVAL=2000 # Optional: Capture settings API_STATS_CAPTURE_BODY=false API_STATS_CAPTURE_HEADERS=true # Project Info (for reference) API_STATS_PROJECT_ID=${config.projectId || 'auto-generated'} `; const envPath = path.join(process.cwd(), '.env.api-stats'); fs.writeFileSync(envPath, envContent); this.ui.info('📝 Criado: .env.api-stats'); } async createIntegrationExample(config) { const fs = require('fs'); const path = require('path'); const framework = config.framework || 'express'; const examples = { express: `// API Stats Logger - Express.js Integration Example require('dotenv').config(); const express = require('express'); const ApiStatsLogger = require('api-stats-logger'); const app = express(); // Initialize API Stats Logger const logger = new ApiStatsLogger({ apiKey: process.env.API_STATS_API_KEY, service: process.env.API_STATS_SERVICE, environment: process.env.API_STATS_ENVIRONMENT, autoInstrument: true }); // Apply middleware app.use(logger.middleware()); // Your existing routes app.get('/', (req, res) => { res.json({ message: 'Hello World!' }); }); app.get('/api/users', (req, res) => { res.json({ users: [{ id: 1, name: 'John' }] }); }); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(\`Server running on port \${PORT}\`); console.log('✅ API Stats Logger active'); }); `, fastify: `// API Stats Logger - Fastify Integration Example require('dotenv').config(); const fastify = require('fastify')({ logger: true }); const ApiStatsLogger = require('api-stats-logger'); // Initialize API Stats Logger const logger = new ApiStatsLogger({ apiKey: process.env.API_STATS_API_KEY, service: process.env.API_STATS_SERVICE, environment: process.env.API_STATS_ENVIRONMENT }); // Register plugin fastify.register(logger.fastifyPlugin()); // Your existing routes fastify.get('/', async (request, reply) => { return { message: 'Hello World!' }; }); fastify.get('/api/users', async (request, reply) => { return { users: [{ id: 1, name: 'John' }] }; }); const start = async () => { try { await fastify.listen(3000); console.log('✅ API Stats Logger active'); } catch (err) { fastify.log.error(err); process.exit(1); } }; start(); `, nestjs: `// API Stats Logger - NestJS Integration Example // main.ts import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { ApiStatsLogger } from 'api-stats-logger'; async function bootstrap() { const app = await NestFactory.create(AppModule); // Initialize API Stats Logger const logger = new ApiStatsLogger({ apiKey: process.env.API_STATS_API_KEY, service: process.env.API_STATS_SERVICE, environment: process.env.API_STATS_ENVIRONMENT }); // Apply as global middleware app.use(logger.middleware()); await app.listen(3000); console.log('✅ API Stats Logger active'); } bootstrap(); ` }; const exampleContent = examples[framework] || examples.express; const fileName = `api-stats-${framework}-example.js`; const filePath = path.join(process.cwd(), fileName); fs.writeFileSync(filePath, exampleContent); this.ui.info(`📝 Criado: ${fileName}`); } async createConfigFile(config) { const fs = require('fs'); const path = require('path'); const configContent = `// API Stats Logger Configuration module.exports = { // Basic settings service: '${config.service}', environment: '${config.environment}', framework: '${config.framework || 'express'}', // Features autoInstrument: true, captureBody: false, captureHeaders: true, // Performance settings batchSize: 10, flushInterval: 2000, maxRetries: 3, // Skip patterns skipPaths: ['/health', '/metrics', '/favicon.ico'], skipMethods: ['OPTIONS'], // Security maxBodySize: 1024 * 10, // 10KB sensitiveHeaders: ['authorization', 'cookie', 'x-api-key'] }; `; const configPath = path.join(process.cwd(), 'api-stats.config.js'); fs.writeFileSync(configPath, configContent); this.ui.info('📝 Criado: api-stats.config.js'); } async createSetupInstructions(config) { const fs = require('fs'); const path = require('path'); const instructions = `# API Stats Logger - Setup Completo ## ✅ Configuração realizada: - **Framework**: ${config.framework || 'express'} - **Serviço**: ${config.service} - **Ambiente**: ${config.environment} - **API Key**: Configurada - **Auto-instrumentação**: Ativada ## 📋 Próximos passos: ### 1. Instalar dependências \`\`\`bash npm install api-stats-logger # ou yarn add api-stats-logger \`\`\` ### 2. Configurar variáveis de ambiente Copie as variáveis do arquivo \`.env.api-stats\` para seu \`.env\` principal: \`\`\`env API_STATS_API_KEY=${config.apiKey} API_STATS_SERVICE=${config.service} API_STATS_ENVIRONMENT=${config.environment} \`\`\` ### 3. Integrar no seu código Veja o arquivo de exemplo: \`api-stats-${config.framework || 'express'}-example.js\` ### 4. Verificar logs - Execute sua aplicação - Faça algumas requisições - Verifique os logs no dashboard: https://apistats.eway.app.br ## 🔧 Configurações avançadas Edite o arquivo \`api-stats.config.js\` para personalizar: - Paths para ignorar - Tamanho máximo do body - Intervals de flush - Headers sensíveis ## 📊 Monitoramento Acesse o dashboard para visualizar: - Métricas de performance - Logs em tempo real - Alertas automáticos ## 🆘 Suporte - Documentação: [GitHub](https://github.com/grupo-loyalty/api-stats-logger) - Issues: [GitHub Issues](https://github.com/grupo-loyalty/api-stats-logger/issues) - Email: dev@grupoloyalty.com.br --- Gerado pelo API Stats CLI v2.2.5 em ${new Date().toISOString()} `; const readmePath = path.join(process.cwd(), 'API-STATS-SETUP.md'); fs.writeFileSync(readmePath, instructions); this.ui.info('📝 Criado: API-STATS-SETUP.md'); } detectFramework() { const fs = require('fs'); const path = require('path'); try { const packageJsonPath = path.join(process.cwd(), 'package.json'); if (fs.existsSync(packageJsonPath)) { const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies }; if (dependencies['@nestjs/core']) return 'nestjs'; if (dependencies['fastify']) return 'fastify'; if (dependencies['koa']) return 'koa'; if (dependencies['express']) return 'express'; } } catch (error) { // Ignore errors } return 'express'; // default } } // Executar CLI se chamado diretamente if (require.main === module) { const cli = new ModernApiStatsCLI(); cli.run().catch(console.error); } module.exports = { ModernApiStatsCLI };