feat: update ESLint and TypeScript configurations across frontend and backend; enhance component structure and improve data handling in various modules
This commit is contained in:
@@ -1,11 +1,8 @@
|
||||
import { v } from "convex/values";
|
||||
import { mutation, query, internalMutation, internalQuery, action } from "./_generated/server";
|
||||
import { internal, api } from "./_generated/api";
|
||||
import {
|
||||
renderizarTemplateEmailFromDoc,
|
||||
type VariaveisTemplate,
|
||||
} from "./templatesMensagens";
|
||||
import type { Doc, Id } from "./_generated/dataModel";
|
||||
import { v } from 'convex/values';
|
||||
import { mutation, query, internalMutation, internalQuery, action } from './_generated/server';
|
||||
import { internal, api } from './_generated/api';
|
||||
import { renderizarTemplateEmailFromDoc, type VariaveisTemplate } from './templatesMensagens';
|
||||
import type { Doc, Id } from './_generated/dataModel';
|
||||
|
||||
// ========== INTERNAL QUERIES ==========
|
||||
|
||||
@@ -13,45 +10,45 @@ import type { Doc, Id } from "./_generated/dataModel";
|
||||
* Obter email por ID (internal query)
|
||||
*/
|
||||
export const getEmailById = internalQuery({
|
||||
args: {
|
||||
emailId: v.id("notificacoesEmail"),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db.get(args.emailId);
|
||||
},
|
||||
args: {
|
||||
emailId: v.id('notificacoesEmail')
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
return await ctx.db.get(args.emailId);
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Obter configuração SMTP ativa (internal query)
|
||||
*/
|
||||
export const getActiveEmailConfig = internalQuery({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
const config = await ctx.db
|
||||
.query("configuracaoEmail")
|
||||
.withIndex("by_ativo", (q) => q.eq("ativo", true))
|
||||
.first();
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
const config = await ctx.db
|
||||
.query('configuracaoEmail')
|
||||
.withIndex('by_ativo', (q) => q.eq('ativo', true))
|
||||
.first();
|
||||
|
||||
return config;
|
||||
},
|
||||
return config;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Listar emails pendentes (internal query)
|
||||
*/
|
||||
export const listarEmailsPendentes = internalQuery({
|
||||
args: {
|
||||
limite: v.optional(v.number()),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const emails = await ctx.db
|
||||
.query("notificacoesEmail")
|
||||
.withIndex("by_status", (q) => q.eq("status", "pendente"))
|
||||
.order("asc") // Mais antigos primeiro
|
||||
.take(args.limite || 10);
|
||||
args: {
|
||||
limite: v.optional(v.number())
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const emails = await ctx.db
|
||||
.query('notificacoesEmail')
|
||||
.withIndex('by_status', (q) => q.eq('status', 'pendente'))
|
||||
.order('asc') // Mais antigos primeiro
|
||||
.take(args.limite || 10);
|
||||
|
||||
return emails;
|
||||
},
|
||||
return emails;
|
||||
}
|
||||
});
|
||||
|
||||
// ========== INTERNAL MUTATIONS ==========
|
||||
@@ -60,51 +57,51 @@ export const listarEmailsPendentes = internalQuery({
|
||||
* Marcar email como enviando (internal mutation)
|
||||
*/
|
||||
export const markEmailEnviando = internalMutation({
|
||||
args: {
|
||||
emailId: v.id("notificacoesEmail"),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const email = await ctx.db.get(args.emailId);
|
||||
if (!email) return;
|
||||
args: {
|
||||
emailId: v.id('notificacoesEmail')
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const email = await ctx.db.get(args.emailId);
|
||||
if (!email) return;
|
||||
|
||||
await ctx.db.patch(args.emailId, {
|
||||
status: "enviando",
|
||||
ultimaTentativa: Date.now(),
|
||||
tentativas: email.tentativas + 1,
|
||||
});
|
||||
},
|
||||
await ctx.db.patch(args.emailId, {
|
||||
status: 'enviando',
|
||||
ultimaTentativa: Date.now(),
|
||||
tentativas: email.tentativas + 1
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Marcar email como enviado (internal mutation)
|
||||
*/
|
||||
export const markEmailEnviado = internalMutation({
|
||||
args: {
|
||||
emailId: v.id("notificacoesEmail"),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.patch(args.emailId, {
|
||||
status: "enviado",
|
||||
enviadoEm: Date.now(),
|
||||
});
|
||||
},
|
||||
args: {
|
||||
emailId: v.id('notificacoesEmail')
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.patch(args.emailId, {
|
||||
status: 'enviado',
|
||||
enviadoEm: Date.now()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Marcar email como falha (internal mutation)
|
||||
*/
|
||||
export const markEmailFalha = internalMutation({
|
||||
args: {
|
||||
emailId: v.id("notificacoesEmail"),
|
||||
erro: v.string(),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.patch(args.emailId, {
|
||||
status: "falha",
|
||||
erroDetalhes: args.erro,
|
||||
ultimaTentativa: Date.now(),
|
||||
});
|
||||
},
|
||||
args: {
|
||||
emailId: v.id('notificacoesEmail'),
|
||||
erro: v.string()
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.patch(args.emailId, {
|
||||
status: 'falha',
|
||||
erroDetalhes: args.erro,
|
||||
ultimaTentativa: Date.now()
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ========== PUBLIC MUTATIONS ==========
|
||||
@@ -113,136 +110,134 @@ export const markEmailFalha = internalMutation({
|
||||
* Enfileirar email para envio assíncrono
|
||||
*/
|
||||
export const enfileirarEmail = mutation({
|
||||
args: {
|
||||
destinatario: v.string(),
|
||||
destinatarioId: v.optional(v.id("usuarios")),
|
||||
assunto: v.string(),
|
||||
corpo: v.string(),
|
||||
templateId: v.optional(v.id("templatesMensagens")),
|
||||
enviadoPor: v.id("usuarios"), // Obrigatório conforme schema
|
||||
agendadaPara: v.optional(v.number()), // timestamp opcional para agendamento
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
// Validar agendamento se fornecido
|
||||
if (args.agendadaPara !== undefined && args.agendadaPara <= Date.now()) {
|
||||
throw new Error("Data de agendamento deve ser futura");
|
||||
}
|
||||
args: {
|
||||
destinatario: v.string(),
|
||||
destinatarioId: v.optional(v.id('usuarios')),
|
||||
assunto: v.string(),
|
||||
corpo: v.string(),
|
||||
templateId: v.optional(v.id('templatesMensagens')),
|
||||
enviadoPor: v.id('usuarios'), // Obrigatório conforme schema
|
||||
agendadaPara: v.optional(v.number()) // timestamp opcional para agendamento
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
// Validar agendamento se fornecido
|
||||
if (args.agendadaPara !== undefined && args.agendadaPara <= Date.now()) {
|
||||
throw new Error('Data de agendamento deve ser futura');
|
||||
}
|
||||
|
||||
const emailId = await ctx.db.insert("notificacoesEmail", {
|
||||
destinatario: args.destinatario,
|
||||
destinatarioId: args.destinatarioId,
|
||||
assunto: args.assunto,
|
||||
corpo: args.corpo,
|
||||
templateId: args.templateId,
|
||||
status: "pendente",
|
||||
tentativas: 0,
|
||||
criadoEm: Date.now(),
|
||||
enviadoPor: args.enviadoPor,
|
||||
agendadaPara: args.agendadaPara,
|
||||
});
|
||||
const emailId = await ctx.db.insert('notificacoesEmail', {
|
||||
destinatario: args.destinatario,
|
||||
destinatarioId: args.destinatarioId,
|
||||
assunto: args.assunto,
|
||||
corpo: args.corpo,
|
||||
templateId: args.templateId,
|
||||
status: 'pendente',
|
||||
tentativas: 0,
|
||||
criadoEm: Date.now(),
|
||||
enviadoPor: args.enviadoPor,
|
||||
agendadaPara: args.agendadaPara
|
||||
});
|
||||
|
||||
// Processar imediatamente se não houver agendamento ou se o agendamento já passou
|
||||
const agora = Date.now();
|
||||
const deveProcessarAgora =
|
||||
args.agendadaPara === undefined ||
|
||||
args.agendadaPara <= agora;
|
||||
// Processar imediatamente se não houver agendamento ou se o agendamento já passou
|
||||
const agora = Date.now();
|
||||
const deveProcessarAgora = args.agendadaPara === undefined || args.agendadaPara <= agora;
|
||||
|
||||
if (deveProcessarAgora) {
|
||||
// Agendar envio imediato via action (não bloqueia a mutation)
|
||||
ctx.scheduler
|
||||
.runAfter(0, api.actions.email.enviar, {
|
||||
emailId: emailId,
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.error(`Erro ao agendar envio imediato de email ${emailId}:`, errorMessage);
|
||||
// Não falha a mutation se houver erro ao agendar - o cron pode processar depois
|
||||
});
|
||||
}
|
||||
// Emails agendados para o futuro serão processados pelo cron quando a hora chegar
|
||||
if (deveProcessarAgora) {
|
||||
// Agendar envio imediato via action (não bloqueia a mutation)
|
||||
ctx.scheduler
|
||||
.runAfter(0, api.actions.email.enviar, {
|
||||
emailId: emailId
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.error(`Erro ao agendar envio imediato de email ${emailId}:`, errorMessage);
|
||||
// Não falha a mutation se houver erro ao agendar - o cron pode processar depois
|
||||
});
|
||||
}
|
||||
// Emails agendados para o futuro serão processados pelo cron quando a hora chegar
|
||||
|
||||
return emailId;
|
||||
},
|
||||
return emailId;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Cancelar agendamento de email
|
||||
*/
|
||||
export const cancelarAgendamentoEmail = mutation({
|
||||
args: {
|
||||
emailId: v.id("notificacoesEmail"),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const email = await ctx.db.get(args.emailId);
|
||||
if (!email) {
|
||||
return { sucesso: false, erro: "Email não encontrado" };
|
||||
}
|
||||
args: {
|
||||
emailId: v.id('notificacoesEmail')
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const email = await ctx.db.get(args.emailId);
|
||||
if (!email) {
|
||||
return { sucesso: false, erro: 'Email não encontrado' };
|
||||
}
|
||||
|
||||
if (email.status !== "pendente") {
|
||||
return { sucesso: false, erro: "Apenas emails pendentes podem ser cancelados" };
|
||||
}
|
||||
if (email.status !== 'pendente') {
|
||||
return { sucesso: false, erro: 'Apenas emails pendentes podem ser cancelados' };
|
||||
}
|
||||
|
||||
// Remove o email da fila
|
||||
await ctx.db.delete(args.emailId);
|
||||
return { sucesso: true };
|
||||
},
|
||||
// Remove o email da fila
|
||||
await ctx.db.delete(args.emailId);
|
||||
return { sucesso: true };
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Enviar email usando template
|
||||
*/
|
||||
export const enviarEmailComTemplate = action({
|
||||
args: {
|
||||
destinatario: v.string(),
|
||||
destinatarioId: v.optional(v.id("usuarios")),
|
||||
templateCodigo: v.string(),
|
||||
variaveis: v.optional(v.record(v.string(), v.string())),
|
||||
enviadoPor: v.id("usuarios"), // Obrigatório conforme schema
|
||||
agendadaPara: v.optional(v.number()), // timestamp opcional para agendamento
|
||||
},
|
||||
handler: async (ctx, args): Promise<Id<"notificacoesEmail">> => {
|
||||
// Buscar template
|
||||
const template: Doc<"templatesMensagens"> | null = await ctx.runQuery(
|
||||
api.templatesMensagens.obterTemplatePorCodigo,
|
||||
{
|
||||
codigo: args.templateCodigo,
|
||||
}
|
||||
);
|
||||
args: {
|
||||
destinatario: v.string(),
|
||||
destinatarioId: v.optional(v.id('usuarios')),
|
||||
templateCodigo: v.string(),
|
||||
variaveis: v.optional(v.record(v.string(), v.string())),
|
||||
enviadoPor: v.id('usuarios'), // Obrigatório conforme schema
|
||||
agendadaPara: v.optional(v.number()) // timestamp opcional para agendamento
|
||||
},
|
||||
handler: async (ctx, args): Promise<Id<'notificacoesEmail'>> => {
|
||||
// Buscar template
|
||||
const template: Doc<'templatesMensagens'> | null = await ctx.runQuery(
|
||||
api.templatesMensagens.obterTemplatePorCodigo,
|
||||
{
|
||||
codigo: args.templateCodigo
|
||||
}
|
||||
);
|
||||
|
||||
if (!template) {
|
||||
throw new Error(`Template não encontrado: ${args.templateCodigo}`);
|
||||
}
|
||||
if (!template) {
|
||||
throw new Error(`Template não encontrado: ${args.templateCodigo}`);
|
||||
}
|
||||
|
||||
// Renderizar template com variáveis
|
||||
const variaveisTemplate: VariaveisTemplate = args.variaveis ?? {};
|
||||
// Renderizar template com variáveis
|
||||
const variaveisTemplate: VariaveisTemplate = args.variaveis ?? {};
|
||||
|
||||
// Garantir que urlSistema sempre tenha protocolo se presente
|
||||
if (
|
||||
typeof variaveisTemplate.urlSistema === "string" &&
|
||||
!variaveisTemplate.urlSistema.match(/^https?:\/\//i)
|
||||
) {
|
||||
variaveisTemplate.urlSistema = `http://${variaveisTemplate.urlSistema}`;
|
||||
}
|
||||
// Garantir que urlSistema sempre tenha protocolo se presente
|
||||
if (
|
||||
typeof variaveisTemplate.urlSistema === 'string' &&
|
||||
!variaveisTemplate.urlSistema.match(/^https?:\/\//i)
|
||||
) {
|
||||
variaveisTemplate.urlSistema = `http://${variaveisTemplate.urlSistema}`;
|
||||
}
|
||||
|
||||
const emailRenderizado = renderizarTemplateEmailFromDoc(template, variaveisTemplate);
|
||||
const emailRenderizado = renderizarTemplateEmailFromDoc(template, variaveisTemplate);
|
||||
|
||||
// Enfileirar email via mutation
|
||||
const emailId: Id<"notificacoesEmail"> = await ctx.runMutation(api.email.enfileirarEmail, {
|
||||
destinatario: args.destinatario,
|
||||
destinatarioId: args.destinatarioId,
|
||||
assunto: emailRenderizado.titulo,
|
||||
corpo: emailRenderizado.html, // HTML completo com wrapper
|
||||
templateId: template._id, // template._id sempre existe se template não é null
|
||||
enviadoPor: args.enviadoPor,
|
||||
agendadaPara: args.agendadaPara,
|
||||
});
|
||||
// Enfileirar email via mutation
|
||||
const emailId: Id<'notificacoesEmail'> = await ctx.runMutation(api.email.enfileirarEmail, {
|
||||
destinatario: args.destinatario,
|
||||
destinatarioId: args.destinatarioId,
|
||||
assunto: emailRenderizado.titulo,
|
||||
corpo: emailRenderizado.html, // HTML completo com wrapper
|
||||
templateId: template._id, // template._id sempre existe se template não é null
|
||||
enviadoPor: args.enviadoPor,
|
||||
agendadaPara: args.agendadaPara
|
||||
});
|
||||
|
||||
if (!emailId) {
|
||||
throw new Error("Erro ao enfileirar email: ID não retornado");
|
||||
}
|
||||
if (!emailId) {
|
||||
throw new Error('Erro ao enfileirar email: ID não retornado');
|
||||
}
|
||||
|
||||
return emailId;
|
||||
},
|
||||
return emailId;
|
||||
}
|
||||
});
|
||||
|
||||
// ========== INTERNAL MUTATION (CRON) ==========
|
||||
@@ -251,47 +246,46 @@ export const enviarEmailComTemplate = action({
|
||||
* Processar fila de emails pendentes (chamado pelo cron)
|
||||
*/
|
||||
export const processarFilaEmails = internalMutation({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
const agora = Date.now();
|
||||
|
||||
// Buscar emails pendentes que devem ser processados agora
|
||||
// (sem agendamento OU com agendamento que já passou)
|
||||
const emailsParaProcessar = await ctx.db
|
||||
.query("notificacoesEmail")
|
||||
.filter((q) => {
|
||||
const statusPendente = q.eq(q.field("status"), "pendente");
|
||||
const semAgendamento = q.eq(q.field("agendadaPara"), undefined);
|
||||
const agendamentoJaPassou = q.and(
|
||||
q.neq(q.field("agendadaPara"), undefined),
|
||||
q.lte(q.field("agendadaPara"), agora)
|
||||
);
|
||||
|
||||
return q.and(
|
||||
statusPendente,
|
||||
q.or(semAgendamento, agendamentoJaPassou)
|
||||
);
|
||||
})
|
||||
.order("asc") // Mais antigos primeiro
|
||||
.take(10);
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
const agora = Date.now();
|
||||
|
||||
if (emailsParaProcessar.length === 0) {
|
||||
return { processados: 0 };
|
||||
}
|
||||
// Buscar emails pendentes que devem ser processados agora
|
||||
// (sem agendamento OU com agendamento que já passou)
|
||||
const emailsParaProcessar = await ctx.db
|
||||
.query('notificacoesEmail')
|
||||
.filter((q) => {
|
||||
const statusPendente = q.eq(q.field('status'), 'pendente');
|
||||
const semAgendamento = q.eq(q.field('agendadaPara'), undefined);
|
||||
const agendamentoJaPassou = q.and(
|
||||
q.neq(q.field('agendadaPara'), undefined),
|
||||
q.lte(q.field('agendadaPara'), agora)
|
||||
);
|
||||
|
||||
// Agendar envio de cada email via action
|
||||
for (const email of emailsParaProcessar) {
|
||||
// Agendar envio assíncrono (não bloqueia o cron)
|
||||
ctx.scheduler.runAfter(0, api.actions.email.enviar, {
|
||||
emailId: email._id,
|
||||
}).catch((error: unknown) => {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.error(`Erro ao agendar envio de email ${email._id}:`, errorMessage);
|
||||
});
|
||||
}
|
||||
return q.and(statusPendente, q.or(semAgendamento, agendamentoJaPassou));
|
||||
})
|
||||
.order('asc') // Mais antigos primeiro
|
||||
.take(10);
|
||||
|
||||
return { processados: emailsParaProcessar.length };
|
||||
}
|
||||
if (emailsParaProcessar.length === 0) {
|
||||
return { processados: 0 };
|
||||
}
|
||||
|
||||
// Agendar envio de cada email via action
|
||||
for (const email of emailsParaProcessar) {
|
||||
// Agendar envio assíncrono (não bloqueia o cron)
|
||||
ctx.scheduler
|
||||
.runAfter(0, api.actions.email.enviar, {
|
||||
emailId: email._id
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.error(`Erro ao agendar envio de email ${email._id}:`, errorMessage);
|
||||
});
|
||||
}
|
||||
|
||||
return { processados: emailsParaProcessar.length };
|
||||
}
|
||||
});
|
||||
|
||||
// ========== QUERIES ==========
|
||||
@@ -300,138 +294,138 @@ export const processarFilaEmails = internalMutation({
|
||||
* Listar emails da fila (para monitoramento)
|
||||
*/
|
||||
export const listarFilaEmails = query({
|
||||
args: {
|
||||
limite: v.optional(v.number()),
|
||||
status: v.optional(v.union(
|
||||
v.literal("pendente"),
|
||||
v.literal("enviando"),
|
||||
v.literal("enviado"),
|
||||
v.literal("falha")
|
||||
)),
|
||||
_refresh: v.optional(v.number()), // Parâmetro ignorado, usado apenas para forçar refresh no frontend
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
let emails;
|
||||
args: {
|
||||
limite: v.optional(v.number()),
|
||||
status: v.optional(
|
||||
v.union(
|
||||
v.literal('pendente'),
|
||||
v.literal('enviando'),
|
||||
v.literal('enviado'),
|
||||
v.literal('falha')
|
||||
)
|
||||
),
|
||||
_refresh: v.optional(v.number()) // Parâmetro ignorado, usado apenas para forçar refresh no frontend
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
let emails;
|
||||
|
||||
// Filtrar por status se fornecido
|
||||
if (args.status) {
|
||||
emails = await ctx.db
|
||||
.query("notificacoesEmail")
|
||||
.withIndex("by_status", (q) => q.eq("status", args.status!))
|
||||
.order("desc")
|
||||
.take(args.limite || 50);
|
||||
} else {
|
||||
// Sem filtro, buscar todos e ordenar por data de criação
|
||||
const todosEmails = await ctx.db.query("notificacoesEmail").collect();
|
||||
todosEmails.sort((a, b) => b.criadoEm - a.criadoEm);
|
||||
emails = todosEmails.slice(0, args.limite || 50);
|
||||
}
|
||||
// Filtrar por status se fornecido
|
||||
if (args.status) {
|
||||
emails = await ctx.db
|
||||
.query('notificacoesEmail')
|
||||
.withIndex('by_status', (q) => q.eq('status', args.status!))
|
||||
.order('desc')
|
||||
.take(args.limite || 50);
|
||||
} else {
|
||||
// Sem filtro, buscar todos e ordenar por data de criação
|
||||
const todosEmails = await ctx.db.query('notificacoesEmail').collect();
|
||||
todosEmails.sort((a, b) => b.criadoEm - a.criadoEm);
|
||||
emails = todosEmails.slice(0, args.limite || 50);
|
||||
}
|
||||
|
||||
return emails;
|
||||
},
|
||||
return emails;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Obter estatísticas da fila de emails (para debug e monitoramento)
|
||||
*/
|
||||
export const obterEstatisticasFilaEmails = query({
|
||||
args: {
|
||||
_refresh: v.optional(v.number()), // Parâmetro ignorado, usado apenas para forçar refresh no frontend
|
||||
},
|
||||
returns: v.object({
|
||||
pendentes: v.number(),
|
||||
enviando: v.number(),
|
||||
enviados: v.number(),
|
||||
falhas: v.number(),
|
||||
total: v.number(),
|
||||
}),
|
||||
handler: async (ctx) => {
|
||||
const todosEmails = await ctx.db.query("notificacoesEmail").collect();
|
||||
|
||||
const estatisticas = {
|
||||
pendentes: 0,
|
||||
enviando: 0,
|
||||
enviados: 0,
|
||||
falhas: 0,
|
||||
total: todosEmails.length,
|
||||
};
|
||||
args: {
|
||||
_refresh: v.optional(v.number()) // Parâmetro ignorado, usado apenas para forçar refresh no frontend
|
||||
},
|
||||
returns: v.object({
|
||||
pendentes: v.number(),
|
||||
enviando: v.number(),
|
||||
enviados: v.number(),
|
||||
falhas: v.number(),
|
||||
total: v.number()
|
||||
}),
|
||||
handler: async (ctx) => {
|
||||
const todosEmails = await ctx.db.query('notificacoesEmail').collect();
|
||||
|
||||
for (const email of todosEmails) {
|
||||
switch (email.status) {
|
||||
case "pendente":
|
||||
estatisticas.pendentes++;
|
||||
break;
|
||||
case "enviando":
|
||||
estatisticas.enviando++;
|
||||
break;
|
||||
case "enviado":
|
||||
estatisticas.enviados++;
|
||||
break;
|
||||
case "falha":
|
||||
estatisticas.falhas++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
const estatisticas = {
|
||||
pendentes: 0,
|
||||
enviando: 0,
|
||||
enviados: 0,
|
||||
falhas: 0,
|
||||
total: todosEmails.length
|
||||
};
|
||||
|
||||
return estatisticas;
|
||||
},
|
||||
for (const email of todosEmails) {
|
||||
switch (email.status) {
|
||||
case 'pendente':
|
||||
estatisticas.pendentes++;
|
||||
break;
|
||||
case 'enviando':
|
||||
estatisticas.enviando++;
|
||||
break;
|
||||
case 'enviado':
|
||||
estatisticas.enviados++;
|
||||
break;
|
||||
case 'falha':
|
||||
estatisticas.falhas++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return estatisticas;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Buscar emails por IDs (para monitoramento de status)
|
||||
*/
|
||||
export const buscarEmailsPorIds = query({
|
||||
args: {
|
||||
emailIds: v.array(v.id("notificacoesEmail")),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const emails = [];
|
||||
for (const emailId of args.emailIds) {
|
||||
const email = await ctx.db.get(emailId);
|
||||
if (email) {
|
||||
emails.push(email);
|
||||
}
|
||||
}
|
||||
return emails;
|
||||
},
|
||||
args: {
|
||||
emailIds: v.array(v.id('notificacoesEmail'))
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const emails = [];
|
||||
for (const emailId of args.emailIds) {
|
||||
const email = await ctx.db.get(emailId);
|
||||
if (email) {
|
||||
emails.push(email);
|
||||
}
|
||||
}
|
||||
return emails;
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Listar agendamentos de email (emails com agendadaPara definido)
|
||||
*/
|
||||
export const listarAgendamentosEmail = query({
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
// Buscar todos os emails agendados (pendentes, enviando ou já enviados que tinham agendamento)
|
||||
const emailsAgendados = await ctx.db
|
||||
.query("notificacoesEmail")
|
||||
.filter((q) => {
|
||||
// Apenas emails que têm agendadaPara definido
|
||||
return q.neq(q.field("agendadaPara"), undefined);
|
||||
})
|
||||
.collect();
|
||||
args: {},
|
||||
handler: async (ctx) => {
|
||||
// Buscar todos os emails agendados (pendentes, enviando ou já enviados que tinham agendamento)
|
||||
const emailsAgendados = await ctx.db
|
||||
.query('notificacoesEmail')
|
||||
.filter((q) => {
|
||||
// Apenas emails que têm agendadaPara definido
|
||||
return q.neq(q.field('agendadaPara'), undefined);
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Enriquecer com informações de destinatário e template
|
||||
const emailsEnriquecidos = await Promise.all(
|
||||
emailsAgendados.map(async (email) => {
|
||||
const destinatarioInfo = email.destinatarioId
|
||||
? await ctx.db.get(email.destinatarioId)
|
||||
: null;
|
||||
|
||||
const templateInfo = email.templateId
|
||||
? await ctx.db.get(email.templateId)
|
||||
: null;
|
||||
// Enriquecer com informações de destinatário e template
|
||||
const emailsEnriquecidos = await Promise.all(
|
||||
emailsAgendados.map(async (email) => {
|
||||
const destinatarioInfo = email.destinatarioId
|
||||
? await ctx.db.get(email.destinatarioId)
|
||||
: null;
|
||||
|
||||
return {
|
||||
...email,
|
||||
destinatarioInfo,
|
||||
templateInfo,
|
||||
};
|
||||
})
|
||||
);
|
||||
const templateInfo = email.templateId ? await ctx.db.get(email.templateId) : null;
|
||||
|
||||
return emailsEnriquecidos;
|
||||
},
|
||||
return {
|
||||
...email,
|
||||
destinatarioInfo,
|
||||
templateInfo
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return emailsEnriquecidos;
|
||||
}
|
||||
});
|
||||
|
||||
// ========== PUBLIC MUTATIONS (MANUAL) ==========
|
||||
@@ -440,53 +434,53 @@ export const listarAgendamentosEmail = query({
|
||||
* Processar fila de emails manualmente (para uso em interface)
|
||||
*/
|
||||
export const processarFilaEmailsManual = action({
|
||||
args: {
|
||||
limite: v.optional(v.number()),
|
||||
},
|
||||
returns: v.object({
|
||||
sucesso: v.boolean(),
|
||||
processados: v.number(),
|
||||
falhas: v.number(),
|
||||
erro: v.optional(v.string()),
|
||||
}),
|
||||
handler: async (ctx, args) => {
|
||||
try {
|
||||
// Buscar emails pendentes
|
||||
const emailsPendentes = await ctx.runQuery(internal.email.listarEmailsPendentes, {
|
||||
limite: args.limite || 10,
|
||||
});
|
||||
args: {
|
||||
limite: v.optional(v.number())
|
||||
},
|
||||
returns: v.object({
|
||||
sucesso: v.boolean(),
|
||||
processados: v.number(),
|
||||
falhas: v.number(),
|
||||
erro: v.optional(v.string())
|
||||
}),
|
||||
handler: async (ctx, args) => {
|
||||
try {
|
||||
// Buscar emails pendentes
|
||||
const emailsPendentes = await ctx.runQuery(internal.email.listarEmailsPendentes, {
|
||||
limite: args.limite || 10
|
||||
});
|
||||
|
||||
if (emailsPendentes.length === 0) {
|
||||
return { sucesso: true, processados: 0, falhas: 0 };
|
||||
}
|
||||
if (emailsPendentes.length === 0) {
|
||||
return { sucesso: true, processados: 0, falhas: 0 };
|
||||
}
|
||||
|
||||
let processados = 0;
|
||||
let falhas = 0;
|
||||
let processados = 0;
|
||||
let falhas = 0;
|
||||
|
||||
// Processar cada email
|
||||
for (const email of emailsPendentes) {
|
||||
try {
|
||||
// Agendar envio via action
|
||||
await ctx.scheduler.runAfter(0, api.actions.email.enviar, {
|
||||
emailId: email._id,
|
||||
});
|
||||
processados++;
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.error(`Erro ao agendar envio de email ${email._id}:`, errorMessage);
|
||||
falhas++;
|
||||
}
|
||||
}
|
||||
// Processar cada email
|
||||
for (const email of emailsPendentes) {
|
||||
try {
|
||||
// Agendar envio via action
|
||||
await ctx.scheduler.runAfter(0, api.actions.email.enviar, {
|
||||
emailId: email._id
|
||||
});
|
||||
processados++;
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
console.error(`Erro ao agendar envio de email ${email._id}:`, errorMessage);
|
||||
falhas++;
|
||||
}
|
||||
}
|
||||
|
||||
return { sucesso: true, processados, falhas };
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
sucesso: false,
|
||||
processados: 0,
|
||||
falhas: 0,
|
||||
erro: errorMessage,
|
||||
};
|
||||
}
|
||||
},
|
||||
return { sucesso: true, processados, falhas };
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
return {
|
||||
sucesso: false,
|
||||
processados: 0,
|
||||
falhas: 0,
|
||||
erro: errorMessage
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user