refactor: optimize login attempt logging in Sidebar component to avoid timeouts by deferring user retrieval; enhance MessageList component with improved message processing and state management to prevent unnecessary re-renders

This commit is contained in:
2025-12-09 01:49:18 -03:00
parent e6f380d7cc
commit 7637cd52f1
3 changed files with 99 additions and 91 deletions

View File

@@ -193,11 +193,10 @@
if (result.data) { if (result.data) {
// Registrar tentativa de login bem-sucedida // Registrar tentativa de login bem-sucedida
// Fazer de forma assíncrona para não bloquear o login // Fazer de forma assíncrona para não bloquear o login
// Não tentamos buscar getCurrentUser aqui porque pode causar timeout
// O useQuery no componente já busca o usuário automaticamente quando a sessão estiver pronta
(async () => { (async () => {
try { try {
// Aguardar um pouco para o usuário ser sincronizado no Convex
await new Promise((resolve) => setTimeout(resolve, 500));
// Tentar obter GPS se já estiver disponível (não esperar) // Tentar obter GPS se já estiver disponível (não esperar)
let localizacaoGPS: any = {}; let localizacaoGPS: any = {};
try { try {
@@ -209,40 +208,21 @@
// Ignorar se GPS não estiver pronto // Ignorar se GPS não estiver pronto
} }
// Buscar o usuário no Convex usando getCurrentUser // Registrar log sem usuarioId - será atualizado depois quando o usuário estiver disponível
const usuario = await convexClient.query(api.auth.getCurrentUser, {}); // Isso evita timeouts que ocorrem quando tentamos buscar getCurrentUser imediatamente após login
await convexClient.mutation(api.logsLogin.registrarTentativaLogin, {
if (usuario && usuario._id) { matriculaOuEmail: matricula.trim(),
await convexClient.mutation(api.logsLogin.registrarTentativaLogin, { sucesso: true,
usuarioId: usuario._id, userAgent: userAgent,
matriculaOuEmail: matricula.trim(), ipAddress: ipPublico,
sucesso: true, latitudeGPS: localizacaoGPS.latitude,
userAgent: userAgent, longitudeGPS: localizacaoGPS.longitude,
ipAddress: ipPublico, precisaoGPS: localizacaoGPS.precisao,
latitudeGPS: localizacaoGPS.latitude, enderecoGPS: localizacaoGPS.endereco,
longitudeGPS: localizacaoGPS.longitude, cidadeGPS: localizacaoGPS.cidade,
precisaoGPS: localizacaoGPS.precisao, estadoGPS: localizacaoGPS.estado,
enderecoGPS: localizacaoGPS.endereco, paisGPS: localizacaoGPS.pais
cidadeGPS: localizacaoGPS.cidade, });
estadoGPS: localizacaoGPS.estado,
paisGPS: localizacaoGPS.pais
});
} else {
// Se não encontrou o usuário, registrar sem usuarioId (será atualizado depois)
await convexClient.mutation(api.logsLogin.registrarTentativaLogin, {
matriculaOuEmail: matricula.trim(),
sucesso: true,
userAgent: userAgent,
ipAddress: ipPublico,
latitudeGPS: localizacaoGPS.latitude,
longitudeGPS: localizacaoGPS.longitude,
precisaoGPS: localizacaoGPS.precisao,
enderecoGPS: localizacaoGPS.endereco,
cidadeGPS: localizacaoGPS.cidade,
estadoGPS: localizacaoGPS.estado,
paisGPS: localizacaoGPS.pais
});
}
} catch (err) { } catch (err) {
console.error('Erro ao registrar tentativa de login:', err); console.error('Erro ao registrar tentativa de login:', err);
// Não bloquear o login se houver erro ao registrar // Não bloquear o login se houver erro ao registrar

View File

@@ -38,48 +38,63 @@
}); });
// Atualizar lista de mensagens quando a query mudar // Atualizar lista de mensagens quando a query mudar
// Usar untrack para evitar loops infinitos
let processandoMensagens = false;
$effect(() => { $effect(() => {
if (processandoMensagens) return;
if (!mensagensQuery?.data) return; if (!mensagensQuery?.data) return;
const resultado = mensagensQuery.data as { mensagens: any[]; hasMore: boolean; nextCursor: Id<'mensagens'> | null }; const resultado = mensagensQuery.data as { mensagens: any[]; hasMore: boolean; nextCursor: Id<'mensagens'> | null };
const novasMensagens = resultado.mensagens || []; const novasMensagens = resultado.mensagens || [];
// Evitar atualizações desnecessárias comparando IDs das mensagens // Comparação simples usando JSON para evitar loops
if (cursor === null) { const idsAtuais = todasMensagens.map(m => String(m?._id)).sort().join(',');
// Primeira carga: verificar se realmente mudou const idsNovos = novasMensagens.map(m => String(m?._id)).sort().join(',');
const idsAtuais = todasMensagens.map(m => m?._id).filter(Boolean);
const idsNovos = novasMensagens.map(m => m?._id).filter(Boolean);
const idsIguais = idsAtuais.length === idsNovos.length &&
idsAtuais.every((id, i) => id === idsNovos[i]);
if (!idsIguais) { // Só atualizar se realmente mudou
todasMensagens = novasMensagens; if (idsAtuais !== idsNovos) {
processandoMensagens = true;
try {
if (cursor === null) {
todasMensagens = novasMensagens;
} else {
// Carregamento adicional: adicionar no início
const idsExistentes = new Set(todasMensagens.map(m => String(m?._id)));
const novasParaAdicionar = novasMensagens.filter(m => m?._id && !idsExistentes.has(String(m._id)));
if (novasParaAdicionar.length > 0) {
todasMensagens = [...novasParaAdicionar, ...todasMensagens];
}
}
hasMore = resultado.hasMore || false;
carregandoMais = false;
} finally {
processandoMensagens = false;
} }
} else { } else {
// Carregamento adicional: adicionar no início (mensagens mais antigas) hasMore = resultado.hasMore || false;
// Verificar se já não foram adicionadas carregandoMais = false;
const idsExistentes = new Set(todasMensagens.map(m => m?._id).filter(Boolean));
const novasParaAdicionar = novasMensagens.filter(m => m?._id && !idsExistentes.has(m._id));
if (novasParaAdicionar.length > 0) {
todasMensagens = [...novasParaAdicionar, ...todasMensagens];
}
} }
hasMore = resultado.hasMore || false;
carregandoMais = false;
}); });
// Resetar quando mudar de conversa // Resetar quando mudar de conversa
let conversaIdAnterior = $state<string | null>(null); let conversaIdAnterior = $state<string | null>(null);
let resetandoConversa = false;
$effect(() => { $effect(() => {
if (resetandoConversa) return;
const conversaIdAtual = String(conversaId); const conversaIdAtual = String(conversaId);
if (conversaIdAnterior !== null && conversaIdAnterior !== conversaIdAtual) { if (conversaIdAnterior !== null && conversaIdAnterior !== conversaIdAtual) {
cursor = null; resetandoConversa = true;
todasMensagens = []; try {
hasMore = true; cursor = null;
carregandoMais = false; todasMensagens = [];
mensagensComConteudo = []; hasMore = true;
carregandoMais = false;
mensagensComConteudo = [];
ultimoProcessamento = '';
} finally {
resetandoConversa = false;
}
} }
conversaIdAnterior = conversaIdAtual; conversaIdAnterior = conversaIdAtual;
}); });
@@ -542,7 +557,10 @@
// Processar mensagens para descriptografar as criptografadas // Processar mensagens para descriptografar as criptografadas
let ultimoProcessamento = $state<string>(''); let ultimoProcessamento = $state<string>('');
let processandoDescriptografia = false;
$effect(() => { $effect(() => {
if (processandoDescriptografia) return;
const mensagens = todasMensagens; const mensagens = todasMensagens;
// Se não há mensagens, limpar e retornar // Se não há mensagens, limpar e retornar
@@ -554,13 +572,14 @@
return; return;
} }
// Criar hash das mensagens para evitar reprocessamento desnecessário // Criar hash simples das mensagens para evitar reprocessamento
const hashMensagens = mensagens.map(m => `${m._id}-${m.criptografado ? '1' : '0'}`).join('|'); const hashMensagens = mensagens.map(m => `${String(m._id)}-${m.criptografado ? '1' : '0'}`).join('|');
if (hashMensagens === ultimoProcessamento) { if (hashMensagens === ultimoProcessamento && mensagensComConteudo.length === mensagens.length) {
return; // Já foi processado return; // Já foi processado
} }
ultimoProcessamento = hashMensagens; ultimoProcessamento = hashMensagens;
processandoDescriptografia = true;
const processarMensagens = async () => { const processarMensagens = async () => {
const processadas: Mensagem[] = []; const processadas: Mensagem[] = [];
@@ -638,6 +657,7 @@
} }
mensagensComConteudo = processadas; mensagensComConteudo = processadas;
processandoDescriptografia = false;
}; };
processarMensagens().catch((error) => { processarMensagens().catch((error) => {
@@ -648,6 +668,7 @@
conteudoDescriptografado: msg.criptografado ? '🔒 Erro ao descriptografar' : msg.conteudo || '', conteudoDescriptografado: msg.criptografado ? '🔒 Erro ao descriptografar' : msg.conteudo || '',
arquivoUrlDescriptografado: msg.criptografado ? null : msg.arquivoUrl || null arquivoUrlDescriptografado: msg.criptografado ? null : msg.arquivoUrl || null
})); }));
processandoDescriptografia = false;
}); });
}); });
@@ -826,7 +847,7 @@
bind:this={messagesContainer} bind:this={messagesContainer}
onscroll={handleScroll} onscroll={handleScroll}
> >
{#if todasMensagens.length > 0 || mensagensComConteudo.length > 0} {#if todasMensagens.length > 0}
{@const mensagensParaExibir = mensagensComConteudo.length > 0 ? mensagensComConteudo : todasMensagens} {@const mensagensParaExibir = mensagensComConteudo.length > 0 ? mensagensComConteudo : todasMensagens}
{@const gruposPorDia = agruparMensagensPorDia(mensagensParaExibir)} {@const gruposPorDia = agruparMensagensPorDia(mensagensParaExibir)}
{#each Object.entries(gruposPorDia) as [dia, mensagensDia]} {#each Object.entries(gruposPorDia) as [dia, mensagensDia]}

View File

@@ -40,32 +40,39 @@ export const createAuth = (
export const getCurrentUser = query({ export const getCurrentUser = query({
args: {}, args: {},
handler: async (ctx) => { handler: async (ctx) => {
const authUser = await authComponent.safeGetAuthUser(ctx); try {
if (!authUser) { const authUser = await authComponent.safeGetAuthUser(ctx);
if (!authUser) {
return;
}
const user = await ctx.db
.query('usuarios')
.withIndex('authId', (q) => q.eq('authId', authUser._id))
.unique();
if (!user) {
return;
}
// Buscar foto de perfil e role em paralelo para melhor performance
const [fotoPerfilUrl, role] = await Promise.all([
user.fotoPerfil ? ctx.storage.getUrl(user.fotoPerfil).catch(() => null) : Promise.resolve(null),
user.roleId
? ctx.db
.query('roles')
.withIndex('by_id', (q) => q.eq('_id', user.roleId))
.unique()
.catch(() => null)
: Promise.resolve(null)
]);
return { ...user, role: role || null, fotoPerfilUrl };
} catch (error) {
// Log do erro mas não falhar completamente - retornar null para permitir retry
console.error('Erro ao buscar usuário atual:', error);
return; return;
} }
const user = await ctx.db
.query('usuarios')
.withIndex('authId', (q) => q.eq('authId', authUser._id))
.unique();
if (!user) {
return;
}
const fotoPerfilUrl = user.fotoPerfil ? await ctx.storage.getUrl(user.fotoPerfil) : null;
if (!user.roleId) {
return { ...user, role: null, fotoPerfilUrl };
}
const role = await ctx.db
.query('roles')
.withIndex('by_id', (q) => q.eq('_id', user.roleId))
.unique();
return { ...user, role, fotoPerfilUrl };
} }
}); });