feat: enhance scheduling and management of email notifications
- Added functionality to cancel scheduled email notifications, improving user control over their email management. - Implemented a query to list all scheduled emails for the current user, providing better visibility into upcoming notifications. - Enhanced the email schema to support scheduling features, including a timestamp for scheduled delivery. - Improved error handling and user feedback for email scheduling actions, ensuring a smoother user experience.
This commit is contained in:
@@ -6,10 +6,44 @@ import {
|
||||
internalMutation,
|
||||
internalQuery,
|
||||
} from "./_generated/server";
|
||||
import { Id } from "./_generated/dataModel";
|
||||
import { Doc, Id } from "./_generated/dataModel";
|
||||
import type { QueryCtx, MutationCtx } from "./_generated/server";
|
||||
import { renderizarTemplate } from "./templatesMensagens";
|
||||
import { internal, api } from "./_generated/api";
|
||||
|
||||
// ========== HELPERS ==========
|
||||
|
||||
/**
|
||||
* Helper function para obter usuário autenticado (Better Auth ou Sessão)
|
||||
*/
|
||||
async function getUsuarioAutenticado(ctx: QueryCtx | MutationCtx): Promise<Doc<"usuarios"> | null> {
|
||||
// Tentar autenticação via Better Auth primeiro
|
||||
const identity = await ctx.auth.getUserIdentity();
|
||||
let usuarioAtual: Doc<"usuarios"> | null = null;
|
||||
|
||||
if (identity && identity.email) {
|
||||
usuarioAtual = await ctx.db
|
||||
.query("usuarios")
|
||||
.withIndex("by_email", (q) => q.eq("email", identity.email!))
|
||||
.first();
|
||||
}
|
||||
|
||||
// Se não encontrou via Better Auth, tentar via sessão mais recente
|
||||
if (!usuarioAtual) {
|
||||
const sessaoAtiva = await ctx.db
|
||||
.query("sessoes")
|
||||
.filter((q) => q.eq(q.field("ativo"), true))
|
||||
.order("desc")
|
||||
.first();
|
||||
|
||||
if (sessaoAtiva) {
|
||||
usuarioAtual = await ctx.db.get(sessaoAtiva.usuarioId);
|
||||
}
|
||||
}
|
||||
|
||||
return usuarioAtual;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enfileirar email para envio
|
||||
*/
|
||||
@@ -187,7 +221,7 @@ export const reenviarEmail = mutation({
|
||||
emailId: v.id("notificacoesEmail"),
|
||||
},
|
||||
returns: v.object({ sucesso: v.boolean() }),
|
||||
handler: async (ctx, args) => {
|
||||
handler: async (ctx, args): Promise<{ sucesso: boolean }> => {
|
||||
const email = await ctx.db.get(args.emailId);
|
||||
if (!email) {
|
||||
return { sucesso: false };
|
||||
@@ -205,6 +239,52 @@ export const reenviarEmail = mutation({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Cancelar agendamento de email
|
||||
*/
|
||||
export const cancelarAgendamentoEmail = mutation({
|
||||
args: {
|
||||
emailId: v.id("notificacoesEmail"),
|
||||
},
|
||||
returns: v.object({ sucesso: v.boolean(), erro: v.optional(v.string()) }),
|
||||
handler: async (ctx, args): Promise<{ sucesso: boolean; erro?: string }> => {
|
||||
const usuarioAtual = await getUsuarioAutenticado(ctx);
|
||||
if (!usuarioAtual) {
|
||||
return { sucesso: false, erro: "Usuário não autenticado" };
|
||||
}
|
||||
|
||||
const email = await ctx.db.get(args.emailId);
|
||||
if (!email) {
|
||||
return { sucesso: false, erro: "Email não encontrado" };
|
||||
}
|
||||
|
||||
// Verificar se o email pertence ao usuário atual
|
||||
if (email.enviadoPor !== usuarioAtual._id) {
|
||||
return { sucesso: false, erro: "Você não tem permissão para cancelar este agendamento" };
|
||||
}
|
||||
|
||||
// Verificar se o email está agendado
|
||||
if (!email.agendadaPara) {
|
||||
return { sucesso: false, erro: "Este email não está agendado" };
|
||||
}
|
||||
|
||||
// Verificar se ainda não foi enviado
|
||||
if (email.status === "enviado") {
|
||||
return { sucesso: false, erro: "Este email já foi enviado" };
|
||||
}
|
||||
|
||||
// Verificar se já passou a data de agendamento
|
||||
if (email.agendadaPara <= Date.now()) {
|
||||
return { sucesso: false, erro: "A data de agendamento já passou" };
|
||||
}
|
||||
|
||||
// Deletar o email agendado
|
||||
await ctx.db.delete(args.emailId);
|
||||
|
||||
return { sucesso: true };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Action para enviar email (será implementado com nodemailer)
|
||||
*
|
||||
@@ -225,8 +305,8 @@ export const buscarEmailsPorIds = query({
|
||||
args: {
|
||||
emailIds: v.array(v.id("notificacoesEmail")),
|
||||
},
|
||||
handler: async (ctx, args) => {
|
||||
const emails = [];
|
||||
handler: async (ctx, args): Promise<Doc<"notificacoesEmail">[]> => {
|
||||
const emails: Doc<"notificacoesEmail">[] = [];
|
||||
for (const emailId of args.emailIds) {
|
||||
const email = await ctx.db.get(emailId);
|
||||
if (email) {
|
||||
@@ -237,6 +317,57 @@ export const buscarEmailsPorIds = query({
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Listar agendamentos de email do usuário atual
|
||||
*/
|
||||
export const listarAgendamentosEmail = query({
|
||||
args: {},
|
||||
handler: async (ctx): Promise<Array<Doc<"notificacoesEmail"> & { destinatarioInfo: Doc<"usuarios"> | null; templateInfo: Doc<"templatesMensagens"> | null }>> => {
|
||||
const usuarioAtual = await getUsuarioAutenticado(ctx);
|
||||
if (!usuarioAtual) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Buscar todos os emails do usuário
|
||||
const todosEmails = await ctx.db
|
||||
.query("notificacoesEmail")
|
||||
.withIndex("by_enviado_por", (q) => q.eq("enviadoPor", usuarioAtual._id))
|
||||
.collect();
|
||||
|
||||
// Filtrar apenas os que têm agendamento (passados ou futuros)
|
||||
const emailsAgendados = todosEmails.filter((email) => email.agendadaPara !== undefined);
|
||||
|
||||
// Enriquecer com informações do destinatário e template
|
||||
const emailsEnriquecidos = await Promise.all(
|
||||
emailsAgendados.map(async (email) => {
|
||||
let destinatarioInfo: Doc<"usuarios"> | null = null;
|
||||
let templateInfo: Doc<"templatesMensagens"> | null = null;
|
||||
|
||||
if (email.destinatarioId) {
|
||||
destinatarioInfo = await ctx.db.get(email.destinatarioId);
|
||||
}
|
||||
|
||||
if (email.templateId) {
|
||||
templateInfo = await ctx.db.get(email.templateId);
|
||||
}
|
||||
|
||||
return {
|
||||
...email,
|
||||
destinatarioInfo,
|
||||
templateInfo,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
// Ordenar por data de agendamento (mais próximos primeiro)
|
||||
return emailsEnriquecidos.sort((a, b) => {
|
||||
const dataA = a.agendadaPara ?? 0;
|
||||
const dataB = b.agendadaPara ?? 0;
|
||||
return dataA - dataB;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const getActiveEmailConfig = internalQuery({
|
||||
args: {},
|
||||
// Tipo inferido automaticamente pelo Convex
|
||||
|
||||
Reference in New Issue
Block a user