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:
@@ -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)
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,8 @@ export const lgpdTables = {
|
||||
v.literal('pendente'),
|
||||
v.literal('em_analise'),
|
||||
v.literal('concluida'),
|
||||
v.literal('rejeitada')
|
||||
v.literal('rejeitada'),
|
||||
v.literal('cancelada')
|
||||
),
|
||||
dadosSolicitados: v.optional(v.string()), // JSON com detalhes da solicitação
|
||||
resposta: v.optional(v.string()), // Resposta da solicitação
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "@sgse-app/backend",
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"dev": "convex dev",
|
||||
"dev": "convex dev",
|
||||
"dev:setup": "convex dev --configure --until-success",
|
||||
"lint": "eslint .",
|
||||
"format": "prettier --write ."
|
||||
|
||||
Reference in New Issue
Block a user