- Updated the `AprovarFerias.svelte` component to use specific types for `solicitacao` and `gestorId`, enhancing type safety. - Improved error handling by refining catch blocks to handle errors more accurately. - Made minor adjustments to ensure consistent code formatting and readability across the component.
364 lines
9.4 KiB
TypeScript
364 lines
9.4 KiB
TypeScript
import { v } from "convex/values";
|
|
import {
|
|
mutation,
|
|
query,
|
|
action,
|
|
internalMutation,
|
|
internalQuery,
|
|
} from "./_generated/server";
|
|
import { Id } from "./_generated/dataModel";
|
|
import { renderizarTemplate } from "./templatesMensagens";
|
|
import { internal, api } from "./_generated/api";
|
|
|
|
/**
|
|
* Enfileirar email para envio
|
|
*/
|
|
export const enfileirarEmail = mutation({
|
|
args: {
|
|
destinatario: v.string(), // email
|
|
destinatarioId: v.optional(v.id("usuarios")),
|
|
assunto: v.string(),
|
|
corpo: v.string(),
|
|
templateId: v.optional(v.id("templatesMensagens")),
|
|
enviadoPorId: v.id("usuarios"),
|
|
},
|
|
returns: v.object({
|
|
sucesso: v.boolean(),
|
|
emailId: v.optional(v.id("notificacoesEmail")),
|
|
}),
|
|
handler: async (ctx, args) => {
|
|
// Validar email
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (!emailRegex.test(args.destinatario)) {
|
|
return { sucesso: false };
|
|
}
|
|
|
|
// Adicionar à fila
|
|
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,
|
|
enviadoPor: args.enviadoPorId,
|
|
criadoEm: Date.now(),
|
|
});
|
|
|
|
return { sucesso: true, emailId };
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Enviar email usando template
|
|
*/
|
|
export const enviarEmailComTemplate = mutation({
|
|
args: {
|
|
destinatario: v.string(),
|
|
destinatarioId: v.optional(v.id("usuarios")),
|
|
templateCodigo: v.string(),
|
|
variaveis: v.record(v.string(), v.string()),
|
|
enviadoPorId: v.id("usuarios"),
|
|
},
|
|
returns: v.object({
|
|
sucesso: v.boolean(),
|
|
emailId: v.optional(v.id("notificacoesEmail")),
|
|
}),
|
|
handler: async (ctx, args) => {
|
|
// Buscar template
|
|
const template = await ctx.db
|
|
.query("templatesMensagens")
|
|
.withIndex("by_codigo", (q) => q.eq("codigo", args.templateCodigo))
|
|
.first();
|
|
|
|
if (!template) {
|
|
console.error("Template não encontrado:", args.templateCodigo);
|
|
return { sucesso: false };
|
|
}
|
|
|
|
// Renderizar template
|
|
const assunto = renderizarTemplate(template.titulo, args.variaveis);
|
|
const corpo = renderizarTemplate(template.corpo, args.variaveis);
|
|
|
|
// Enfileirar email
|
|
const emailId = await ctx.db.insert("notificacoesEmail", {
|
|
destinatario: args.destinatario,
|
|
destinatarioId: args.destinatarioId,
|
|
assunto,
|
|
corpo,
|
|
templateId: template._id,
|
|
status: "pendente",
|
|
tentativas: 0,
|
|
enviadoPor: args.enviadoPorId,
|
|
criadoEm: Date.now(),
|
|
});
|
|
|
|
return { sucesso: true, emailId };
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Listar emails na fila
|
|
*/
|
|
export const listarFilaEmails = query({
|
|
args: {
|
|
status: v.optional(
|
|
v.union(
|
|
v.literal("pendente"),
|
|
v.literal("enviando"),
|
|
v.literal("enviado"),
|
|
v.literal("falha")
|
|
)
|
|
),
|
|
limite: v.optional(v.number()),
|
|
},
|
|
// Tipo inferido automaticamente pelo Convex
|
|
handler: async (ctx, args) => {
|
|
if (args.status) {
|
|
const emails = await ctx.db
|
|
.query("notificacoesEmail")
|
|
.withIndex("by_status", (q) => q.eq("status", args.status!))
|
|
.order("desc")
|
|
.take(args.limite ?? 100);
|
|
return emails;
|
|
}
|
|
|
|
const emails = await ctx.db
|
|
.query("notificacoesEmail")
|
|
.withIndex("by_criado_em")
|
|
.order("desc")
|
|
.take(args.limite ?? 100);
|
|
return emails;
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Reenviar email falhado
|
|
*/
|
|
export const reenviarEmail = mutation({
|
|
args: {
|
|
emailId: v.id("notificacoesEmail"),
|
|
},
|
|
returns: v.object({ sucesso: v.boolean() }),
|
|
handler: async (ctx, args) => {
|
|
const email = await ctx.db.get(args.emailId);
|
|
if (!email) {
|
|
return { sucesso: false };
|
|
}
|
|
|
|
// Resetar status para pendente
|
|
await ctx.db.patch(args.emailId, {
|
|
status: "pendente",
|
|
tentativas: 0,
|
|
ultimaTentativa: undefined,
|
|
erroDetalhes: undefined,
|
|
});
|
|
|
|
return { sucesso: true };
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Action para enviar email (será implementado com nodemailer)
|
|
*
|
|
* NOTA: Este é um placeholder. Implementação real requer nodemailer.
|
|
*/
|
|
export const getEmailById = internalQuery({
|
|
args: { emailId: v.id("notificacoesEmail") },
|
|
// Tipo inferido automaticamente pelo Convex
|
|
handler: async (ctx, args) => {
|
|
return await ctx.db.get(args.emailId);
|
|
},
|
|
});
|
|
|
|
export const getActiveEmailConfig = internalQuery({
|
|
args: {},
|
|
// Tipo inferido automaticamente pelo Convex
|
|
handler: async (ctx) => {
|
|
return await ctx.db
|
|
.query("configuracaoEmail")
|
|
.withIndex("by_ativo", (q) => q.eq("ativo", true))
|
|
.first();
|
|
},
|
|
});
|
|
|
|
export const markEmailEnviando = internalMutation({
|
|
args: { emailId: v.id("notificacoesEmail") },
|
|
returns: v.null(),
|
|
handler: async (ctx, args) => {
|
|
const email = await ctx.db.get(args.emailId);
|
|
if (!email) return null;
|
|
await ctx.db.patch(args.emailId, {
|
|
status: "enviando",
|
|
tentativas: (email.tentativas || 0) + 1,
|
|
ultimaTentativa: Date.now(),
|
|
});
|
|
return null;
|
|
},
|
|
});
|
|
|
|
export const markEmailEnviado = internalMutation({
|
|
args: { emailId: v.id("notificacoesEmail") },
|
|
returns: v.null(),
|
|
handler: async (ctx, args) => {
|
|
await ctx.db.patch(args.emailId, {
|
|
status: "enviado",
|
|
enviadoEm: Date.now(),
|
|
});
|
|
return null;
|
|
},
|
|
});
|
|
|
|
export const markEmailFalha = internalMutation({
|
|
args: { emailId: v.id("notificacoesEmail"), erro: v.string() },
|
|
returns: v.null(),
|
|
handler: async (ctx, args) => {
|
|
const email = await ctx.db.get(args.emailId);
|
|
if (!email) return null;
|
|
await ctx.db.patch(args.emailId, {
|
|
status: "falha",
|
|
erroDetalhes: args.erro,
|
|
tentativas: (email.tentativas || 0) + 1,
|
|
});
|
|
return null;
|
|
},
|
|
});
|
|
|
|
export const enviarEmailAction = action({
|
|
args: {
|
|
emailId: v.id("notificacoesEmail"),
|
|
},
|
|
returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
|
|
handler: async (ctx, args) => {
|
|
"use node";
|
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
const nodemailer = require("nodemailer");
|
|
|
|
try {
|
|
// Buscar email da fila
|
|
const email = await ctx.runQuery(internal.email.getEmailById, {
|
|
emailId: args.emailId,
|
|
});
|
|
|
|
if (!email) {
|
|
return { sucesso: false, erro: "Email não encontrado" };
|
|
}
|
|
|
|
// Buscar configuração SMTP
|
|
const config = await ctx.runQuery(
|
|
internal.email.getActiveEmailConfig,
|
|
{}
|
|
);
|
|
|
|
if (!config) {
|
|
return {
|
|
sucesso: false,
|
|
erro: "Configuração de email não encontrada ou inativa",
|
|
};
|
|
}
|
|
|
|
if (!config.testadoEm) {
|
|
return {
|
|
sucesso: false,
|
|
erro: "Configuração SMTP não foi testada. Teste a conexão primeiro!",
|
|
};
|
|
}
|
|
|
|
// Marcar como enviando
|
|
await ctx.runMutation(internal.email.markEmailEnviando, {
|
|
emailId: args.emailId,
|
|
});
|
|
|
|
// Criar transporter do nodemailer
|
|
const transporter = nodemailer.createTransport({
|
|
host: config.servidor,
|
|
port: config.porta,
|
|
secure: config.usarSSL,
|
|
auth: {
|
|
user: config.usuario,
|
|
pass: config.senhaHash, // Note: em produção deve ser descriptografado
|
|
},
|
|
tls: {
|
|
rejectUnauthorized: false,
|
|
},
|
|
});
|
|
|
|
// Enviar email REAL
|
|
const info = await transporter.sendMail({
|
|
from: `"${config.nomeRemetente}" <${config.emailRemetente}>`,
|
|
to: email.destinatario,
|
|
subject: email.assunto,
|
|
html: email.corpo,
|
|
});
|
|
|
|
console.log("✅ Email enviado com sucesso!");
|
|
console.log(" Para:", email.destinatario);
|
|
console.log(" Assunto:", email.assunto);
|
|
console.log(" Message ID:", info.messageId);
|
|
|
|
// Marcar como enviado
|
|
await ctx.runMutation(internal.email.markEmailEnviado, {
|
|
emailId: args.emailId,
|
|
});
|
|
|
|
return { sucesso: true };
|
|
} catch (error) {
|
|
const errorMessage =
|
|
error instanceof Error ? error.message : String(error);
|
|
console.error("❌ Erro ao enviar email:", errorMessage);
|
|
|
|
// Marcar como falha
|
|
await ctx.runMutation(internal.email.markEmailFalha, {
|
|
emailId: args.emailId,
|
|
erro: errorMessage,
|
|
});
|
|
|
|
return { sucesso: false, erro: errorMessage };
|
|
}
|
|
},
|
|
});
|
|
|
|
/**
|
|
* Processar fila de emails (cron job - processa emails pendentes)
|
|
*/
|
|
export const processarFilaEmails = internalMutation({
|
|
args: {},
|
|
returns: v.object({ processados: v.number() }),
|
|
handler: async (ctx) => {
|
|
// Buscar emails pendentes (max 10 por execução)
|
|
const emailsPendentes = await ctx.db
|
|
.query("notificacoesEmail")
|
|
.withIndex("by_status", (q) => q.eq("status", "pendente"))
|
|
.take(10);
|
|
|
|
let processados = 0;
|
|
|
|
for (const email of emailsPendentes) {
|
|
// Verificar se não excedeu tentativas (max 3)
|
|
if ((email.tentativas || 0) >= 3) {
|
|
await ctx.db.patch(email._id, {
|
|
status: "falha",
|
|
erroDetalhes: "Número máximo de tentativas excedido",
|
|
});
|
|
continue;
|
|
}
|
|
|
|
// Agendar envio via action
|
|
// IMPORTANTE: Não podemos chamar action diretamente de mutation
|
|
// Por isso, usaremos o scheduler com string path
|
|
await ctx.scheduler.runAfter(0, "email:enviarEmailAction" as any, {
|
|
emailId: email._id,
|
|
});
|
|
|
|
processados++;
|
|
}
|
|
|
|
console.log(
|
|
`📧 Fila de emails processada: ${processados} emails agendados para envio`
|
|
);
|
|
|
|
return { processados };
|
|
},
|
|
});
|