feat: implement cancellation and deletion functionality for LGPD requests; enhance UI with confirmation modals and update backend to support new operations

This commit is contained in:
2025-12-03 16:57:10 -03:00
parent b145fcc74a
commit 4a662c08a0
5 changed files with 549 additions and 59 deletions

View File

@@ -289,9 +289,11 @@ export const listarMinhasSolicitacoes = query({
v.literal('pendente'),
v.literal('em_analise'),
v.literal('concluida'),
v.literal('rejeitada')
v.literal('rejeitada'),
v.literal('cancelada')
)
)
),
_refresh: v.optional(v.number()) // Parâmetro para forçar atualização no frontend
},
returns: v.array(
v.object({
@@ -357,7 +359,8 @@ export const listarSolicitacoes = query({
v.literal('pendente'),
v.literal('em_analise'),
v.literal('concluida'),
v.literal('rejeitada')
v.literal('rejeitada'),
v.literal('cancelada')
)
),
tipo: v.optional(
@@ -372,29 +375,33 @@ export const listarSolicitacoes = query({
),
limite: v.optional(v.number())
},
returns: v.array(
v.object({
_id: v.id('solicitacoesLGPD'),
tipo: v.string(),
status: v.string(),
usuarioNome: v.string(),
usuarioEmail: v.string(),
usuarioMatricula: v.union(v.string(), v.null()),
criadoEm: v.number(),
prazoResposta: v.number(),
respondidoEm: v.union(v.number(), v.null()),
respondidoPorNome: v.union(v.string(), v.null()),
consentimentoTermo: v.union(
v.object({
aceito: v.boolean(),
versao: v.string(),
aceitoEm: v.number(),
revogadoEm: v.union(v.number(), v.null())
}),
v.null()
)
})
),
returns: v.array(
v.object({
_id: v.id('solicitacoesLGPD'),
tipo: v.string(),
status: v.string(),
usuarioNome: v.string(),
usuarioEmail: v.string(),
usuarioMatricula: v.union(v.string(), v.null()),
dadosSolicitados: v.union(v.string(), v.null()),
observacoes: v.union(v.string(), v.null()),
resposta: v.union(v.string(), v.null()),
arquivoResposta: v.union(v.string(), v.null()),
criadoEm: v.number(),
prazoResposta: v.number(),
respondidoEm: v.union(v.number(), v.null()),
respondidoPorNome: v.union(v.string(), v.null()),
consentimentoTermo: v.union(
v.object({
aceito: v.boolean(),
versao: v.string(),
aceitoEm: v.number(),
revogadoEm: v.union(v.number(), v.null())
}),
v.null()
)
})
),
handler: async (ctx, args) => {
const usuario = await getCurrentUserFunction(ctx);
if (!usuario) {
@@ -433,6 +440,10 @@ export const listarSolicitacoes = query({
usuarioNome: string;
usuarioEmail: string;
usuarioMatricula: string | null;
dadosSolicitados: string | null;
observacoes: string | null;
resposta: string | null;
arquivoResposta: string | null;
criadoEm: number;
prazoResposta: number;
respondidoEm: number | null;
@@ -497,6 +508,12 @@ export const listarSolicitacoes = query({
}
}
// Buscar URL do arquivo de resposta se existir
let arquivoRespostaUrl: string | null = null;
if (s.arquivoResposta) {
arquivoRespostaUrl = await ctx.storage.getUrl(s.arquivoResposta);
}
return {
_id: s._id,
tipo: s.tipo,
@@ -504,6 +521,10 @@ export const listarSolicitacoes = query({
usuarioNome: usuarioSolicitante?.nome ?? 'Usuário Desconhecido',
usuarioEmail: usuarioSolicitante?.email ?? '',
usuarioMatricula: matricula,
dadosSolicitados: s.dadosSolicitados ?? null,
observacoes: s.observacoes ?? null,
resposta: s.resposta ?? null,
arquivoResposta: arquivoRespostaUrl,
criadoEm: s.criadoEm,
prazoResposta: s.prazoResposta,
respondidoEm: s.respondidoEm ?? null,
@@ -520,6 +541,10 @@ export const listarSolicitacoes = query({
usuarioNome: 'Erro ao carregar',
usuarioEmail: '',
usuarioMatricula: null,
dadosSolicitados: s.dadosSolicitados ?? null,
observacoes: s.observacoes ?? null,
resposta: s.resposta ?? null,
arquivoResposta: null,
criadoEm: s.criadoEm,
prazoResposta: s.prazoResposta,
respondidoEm: s.respondidoEm ?? null,
@@ -583,6 +608,105 @@ export const responderSolicitacao = mutation({
}
});
/**
* Cancelar solicitação LGPD (apenas pelo próprio usuário)
*/
export const cancelarSolicitacao = mutation({
args: {
solicitacaoId: v.id('solicitacoesLGPD')
},
returns: v.object({ sucesso: v.boolean() }),
handler: async (ctx, args) => {
const usuario = await getCurrentUserFunction(ctx);
if (!usuario) {
throw new Error('Usuário não autenticado');
}
const solicitacao = await ctx.db.get(args.solicitacaoId);
if (!solicitacao) {
throw new Error('Solicitação não encontrada');
}
// Verificar se a solicitação pertence ao usuário
if (solicitacao.usuarioId !== usuario._id) {
throw new Error('Você não tem permissão para cancelar esta solicitação');
}
// Só pode cancelar se estiver pendente ou em análise
if (solicitacao.status !== 'pendente' && solicitacao.status !== 'em_analise') {
throw new Error('Só é possível cancelar solicitações pendentes ou em análise');
}
// Atualizar status para cancelada
await ctx.db.patch(args.solicitacaoId, {
status: 'cancelada'
});
// Log de atividade
await registrarAtividade(
ctx,
usuario._id,
'cancelar_solicitacao_lgpd',
'solicitacoesLGPD',
JSON.stringify({ solicitacaoId: args.solicitacaoId }),
args.solicitacaoId.toString()
);
return { sucesso: true };
}
});
/**
* Excluir solicitação LGPD (apenas pelo próprio usuário e apenas se cancelada ou pendente)
*/
export const excluirSolicitacao = mutation({
args: {
solicitacaoId: v.id('solicitacoesLGPD')
},
returns: v.object({ sucesso: v.boolean() }),
handler: async (ctx, args) => {
const usuario = await getCurrentUserFunction(ctx);
if (!usuario) {
throw new Error('Usuário não autenticado');
}
const solicitacao = await ctx.db.get(args.solicitacaoId);
if (!solicitacao) {
throw new Error('Solicitação não encontrada');
}
// Verificar se a solicitação pertence ao usuário
if (solicitacao.usuarioId !== usuario._id) {
throw new Error('Você não tem permissão para excluir esta solicitação');
}
// Só pode excluir se estiver pendente ou cancelada
if (solicitacao.status !== 'pendente' && solicitacao.status !== 'cancelada') {
throw new Error('Só é possível excluir solicitações pendentes ou canceladas');
}
// Excluir arquivo de resposta se existir
if (solicitacao.arquivoResposta) {
await ctx.storage.delete(solicitacao.arquivoResposta);
}
// Excluir a solicitação
await ctx.db.delete(args.solicitacaoId);
// Log de atividade
await registrarAtividade(
ctx,
usuario._id,
'excluir_solicitacao_lgpd',
'solicitacoesLGPD',
JSON.stringify({ solicitacaoId: args.solicitacaoId }),
args.solicitacaoId.toString()
);
return { sucesso: true };
}
});
/**
* Exportar dados do usuário (portabilidade)
*/