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
JavaScript
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 };