382 lines
10 KiB
TypeScript
382 lines
10 KiB
TypeScript
import { Router, Request, Response } from "express";
|
|
import multer from "multer";
|
|
import { startOfWeek, endOfWeek, parseISO } from 'date-fns';
|
|
import { ptBR } from 'date-fns/locale';
|
|
|
|
import { authMiddleware } from "../../middleware/auth";
|
|
import { NextCloudService } from "../../services/nextcloud";
|
|
import { prisma } from "../prismaClient";
|
|
|
|
const router = Router();
|
|
const upload = multer({ storage: multer.memoryStorage() });
|
|
|
|
router.use(authMiddleware);
|
|
|
|
// Middleware para logging com informações mais detalhadas
|
|
router.use((req, res, next) => {
|
|
const requestId = Math.random().toString(36).substring(7);
|
|
console.log(`[${new Date().toISOString()}][${requestId}] Request:`, {
|
|
method: req.method,
|
|
path: req.path,
|
|
originalUrl: req.originalUrl,
|
|
params: req.params,
|
|
query: req.query,
|
|
body: req.body
|
|
});
|
|
|
|
// Interceptar resposta para logging
|
|
const oldJson = res.json;
|
|
res.json = function(data) {
|
|
console.log(`[${new Date().toISOString()}][${requestId}] Response:`, {
|
|
statusCode: res.statusCode,
|
|
data: data
|
|
});
|
|
return oldJson.apply(res, arguments);
|
|
};
|
|
|
|
next();
|
|
});
|
|
|
|
// Função auxiliar para validar se uma string é um UUID válido
|
|
function isValidUUID(uuid: string): boolean {
|
|
if (!uuid) return false;
|
|
|
|
try {
|
|
const regexUUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
return regexUUID.test(uuid);
|
|
} catch (error) {
|
|
console.error("Erro ao validar UUID:", error);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Lista de rotas que não precisam de validação UUID
|
|
const ROUTES_WITHOUT_UUID_VALIDATION = ['agendamentos', 'upload'];
|
|
|
|
// Middleware para validação de UUID em rotas específicas
|
|
router.param('id', (req, res, next) => {
|
|
const path = req.path.split('/')[1];
|
|
|
|
// Se a rota estiver na lista de exceções, permite passar
|
|
if (ROUTES_WITHOUT_UUID_VALIDATION.includes(path)) {
|
|
return next();
|
|
}
|
|
|
|
const { id } = req.params;
|
|
if (!isValidUUID(id)) {
|
|
console.error(`ID inválido: ${id} para rota ${path}`);
|
|
return res.status(400).json({
|
|
error: "ID inválido",
|
|
details: "O formato do ID fornecido não é válido para esta operação"
|
|
});
|
|
}
|
|
|
|
next();
|
|
});
|
|
|
|
// Rota para listar agendamentos - Simplificada e sem validação UUID
|
|
router.get("/agendamentos", async (req: Request, res: Response) => {
|
|
try {
|
|
console.log("[Agendamentos] Iniciando busca");
|
|
const { data } = req.query;
|
|
|
|
if (!data) {
|
|
return res.status(400).json({
|
|
error: "Data é obrigatória",
|
|
details: "Informe uma data válida"
|
|
});
|
|
}
|
|
|
|
const searchDate = new Date(data as string);
|
|
if (isNaN(searchDate.getTime())) {
|
|
return res.status(400).json({
|
|
error: "Data inválida",
|
|
details: "O formato da data deve ser YYYY-MM-DD"
|
|
});
|
|
}
|
|
|
|
const agendamentos = await prisma.agendamentoAtendimento.findMany({
|
|
where: {
|
|
data: {
|
|
gte: startOfWeek(searchDate, { locale: ptBR }),
|
|
lte: endOfWeek(searchDate, { locale: ptBR })
|
|
}
|
|
},
|
|
include: {
|
|
cliente: {
|
|
select: {
|
|
id: true,
|
|
nome: true,
|
|
}
|
|
},
|
|
usuario: {
|
|
select: {
|
|
id: true,
|
|
nome: true,
|
|
}
|
|
}
|
|
},
|
|
orderBy: {
|
|
data: 'asc'
|
|
}
|
|
});
|
|
|
|
return res.json(agendamentos);
|
|
|
|
} catch (error) {
|
|
console.error("[Agendamentos] Erro ao buscar:", error);
|
|
return res.status(500).json({
|
|
error: "Erro ao buscar agendamentos",
|
|
details: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
// Rota para criar um novo agendamento
|
|
router.post("/agendamentos", async (req: Request, res: Response) => {
|
|
try {
|
|
const { clienteId, data, observacoes } = req.body; // Removido tipo temporariamente
|
|
console.log("[Agendamentos] Criando agendamento:", { clienteId, data, observacoes });
|
|
|
|
if (!clienteId || !data) {
|
|
return res.status(400).json({
|
|
error: "Dados inválidos",
|
|
details: "Cliente e data são obrigatórios"
|
|
});
|
|
}
|
|
|
|
// Verificar se o cliente existe
|
|
const cliente = await prisma.cliente.findUnique({
|
|
where: { id: clienteId }
|
|
});
|
|
|
|
if (!cliente) {
|
|
return res.status(404).json({ error: "Cliente não encontrado" });
|
|
}
|
|
|
|
// Criar o agendamento sem o campo tipo
|
|
const agendamento = await prisma.agendamentoAtendimento.create({
|
|
data: {
|
|
clienteId,
|
|
data: new Date(data),
|
|
observacoes,
|
|
status: "AGENDADO",
|
|
usuarioId: req.user?.id
|
|
},
|
|
include: {
|
|
cliente: {
|
|
select: {
|
|
id: true,
|
|
nome: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
console.log("[Agendamentos] Agendamento criado com sucesso:", {
|
|
id: agendamento.id,
|
|
data: agendamento.data,
|
|
clienteNome: agendamento.cliente.nome
|
|
});
|
|
|
|
return res.status(201).json(agendamento);
|
|
} catch (error) {
|
|
console.error("[Agendamentos] Erro ao criar agendamento:", error);
|
|
return res.status(500).json({
|
|
error: "Erro ao criar agendamento",
|
|
details: error.message,
|
|
stack: process.env.NODE_ENV === 'development' ? error.stack : undefined
|
|
});
|
|
}
|
|
});
|
|
|
|
// Rota para atualizar um agendamento
|
|
router.put("/agendamentos/:id", async (req: Request, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { clienteId, data, observacoes, tipo } = req.body;
|
|
|
|
console.log("Atualizando agendamento:", { id, clienteId, data, observacoes, tipo });
|
|
|
|
// Verificar se o agendamento existe usando a tabela correta
|
|
const existingAgendamento = await prisma.agendamentoAtendimento.findUnique({
|
|
where: { id }
|
|
});
|
|
|
|
if (!existingAgendamento) {
|
|
return res.status(404).json({ error: "Agendamento não encontrado" });
|
|
}
|
|
|
|
// Atualizar o agendamento usando a tabela correta
|
|
const agendamento = await prisma.agendamentoAtendimento.update({
|
|
where: { id },
|
|
data: {
|
|
clienteId,
|
|
data: data ? new Date(data) : undefined,
|
|
observacoes,
|
|
tipo
|
|
},
|
|
include: {
|
|
cliente: {
|
|
select: {
|
|
id: true,
|
|
nome: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
console.log("Agendamento atualizado:", {
|
|
id: agendamento.id,
|
|
data: agendamento.data,
|
|
clienteNome: agendamento.cliente.nome
|
|
});
|
|
|
|
res.json(agendamento);
|
|
} catch (error) {
|
|
console.error("Erro ao atualizar agendamento:", error);
|
|
res.status(500).json({ error: "Erro ao atualizar agendamento", details: error.message });
|
|
}
|
|
});
|
|
|
|
// Rota para excluir um agendamento
|
|
router.delete("/agendamentos/:id", async (req: Request, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
console.log("Excluindo agendamento:", id);
|
|
|
|
// Verificar se o agendamento existe usando a tabela correta
|
|
const agendamento = await prisma.agendamentoAtendimento.findUnique({
|
|
where: { id },
|
|
include: { cliente: true }
|
|
});
|
|
|
|
if (!agendamento) {
|
|
console.error("Agendamento não encontrado:", id);
|
|
return res.status(404).json({ error: "Agendamento não encontrado" });
|
|
}
|
|
|
|
console.log("Agendamento encontrado:", {
|
|
id: agendamento.id,
|
|
data: agendamento.data,
|
|
clienteNome: agendamento.cliente?.nome
|
|
});
|
|
|
|
// Excluir o agendamento usando a tabela correta
|
|
await prisma.agendamentoAtendimento.delete({
|
|
where: { id }
|
|
});
|
|
|
|
console.log("Agendamento excluído com sucesso");
|
|
res.status(200).json({ message: "Agendamento excluído com sucesso" });
|
|
} catch (error) {
|
|
console.error("Erro ao excluir agendamento:", error);
|
|
res.status(500).json({ error: "Erro ao excluir agendamento", details: error.message });
|
|
}
|
|
});
|
|
|
|
// Rota para atualizar atendimento com anexos
|
|
router.patch("/:id", async (req: Request, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
const { anexos, ...data } = req.body;
|
|
|
|
console.log("Atualizando atendimento:", id, { anexos, data }); // Debug log
|
|
|
|
const atendimento = await prisma.atendimento.update({
|
|
where: { id },
|
|
data: {
|
|
...data,
|
|
...(anexos && {
|
|
anexos: {
|
|
createMany: {
|
|
data: anexos.map((anexo: any) => ({
|
|
nome: anexo.nome,
|
|
url: anexo.url,
|
|
tipo: anexo.tipo,
|
|
tamanho: anexo.tamanho,
|
|
})),
|
|
},
|
|
},
|
|
}),
|
|
},
|
|
include: {
|
|
anexos: true,
|
|
cliente: true,
|
|
},
|
|
});
|
|
|
|
console.log("Atendimento atualizado:", atendimento); // Debug log
|
|
res.json(atendimento);
|
|
} catch (error) {
|
|
console.error("Erro ao atualizar atendimento:", error);
|
|
res.status(500).json({ error: "Erro ao atualizar atendimento" });
|
|
}
|
|
});
|
|
|
|
// Nova rota para upload de arquivo via proxy
|
|
router.post("/upload", upload.single("file"), async (req: Request, res: Response) => {
|
|
try {
|
|
if (!req.file || !req.body.clienteNome) {
|
|
return res
|
|
.status(400)
|
|
.json({ error: "Arquivo e nome do cliente são obrigatórios" });
|
|
}
|
|
|
|
// Enviar para o NextCloud usando a estrutura de pastas correta
|
|
const buffer = req.file.buffer;
|
|
Object.defineProperty(buffer, "name", {
|
|
value: req.file.originalname,
|
|
writable: true,
|
|
enumerable: true,
|
|
});
|
|
|
|
const filePath = await NextCloudService.uploadFile(
|
|
buffer,
|
|
req.body.clienteNome,
|
|
"atendimento",
|
|
req.body.protocolo,
|
|
);
|
|
|
|
res.json({ url: filePath });
|
|
} catch (error) {
|
|
console.error("Erro no upload:", error);
|
|
res.status(500).json({ error: "Erro no upload do arquivo" });
|
|
}
|
|
});
|
|
|
|
router.get("/:id", async (req: Request, res: Response) => {
|
|
try {
|
|
const { id } = req.params;
|
|
|
|
const atendimento = await prisma.atendimento.findUnique({
|
|
where: {
|
|
id: id,
|
|
},
|
|
include: {
|
|
cliente: true,
|
|
processo: true,
|
|
usuario: {
|
|
select: {
|
|
id: true,
|
|
nome: true,
|
|
oab: true,
|
|
oabUF: true,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
|
|
if (!atendimento) {
|
|
return res.status(404).json({ error: "Atendimento não encontrado" });
|
|
}
|
|
|
|
res.json(atendimento);
|
|
} catch (error) {
|
|
console.error("Erro ao buscar atendimento:", error);
|
|
res.status(500).json({ error: "Erro ao buscar atendimento" });
|
|
}
|
|
});
|
|
|
|
export default router;
|