Files
scoreodonto.com/backend/dist/modules/contacts/contact.routes.js
T

153 lines
6.5 KiB
JavaScript

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.buildContactRoutes = buildContactRoutes;
const express_1 = require("express");
const zod_1 = require("zod");
const prisma_1 = require("../../infra/database/prisma");
const whatsapp_1 = require("../../shared/utils/whatsapp");
const chat_cache_1 = require("../chats/chat.cache");
function buildContactRoutes() {
const router = (0, express_1.Router)();
// ── GET /api/contacts/stats/overview — deve vir ANTES de /:id ─────────────
router.get('/stats/overview', async (req, res) => {
const tenantId = req.tenantId;
const instanceId = req.query['instanceId'];
const where = { tenantId };
if (instanceId)
where['instanceId'] = instanceId;
const [total, blocked, flagged] = await Promise.all([
prisma_1.prisma.contact.count({ where }),
prisma_1.prisma.contact.count({ where: { ...where, isBlocked: true } }),
prisma_1.prisma.contact.count({ where: { ...where, flagRestricao: true } }),
]);
res.json({ total, blocked, flagged, active: total - blocked });
});
// ── GET /api/contacts — lista paginada ────────────────────────────────────
router.get('/', async (req, res) => {
const tenantId = req.tenantId;
const instanceId = req.query['instanceId'];
const search = req.query['search'];
const blocked = req.query['blocked'];
const limit = Math.min(Number(req.query['limit'] ?? 50), 200);
const offset = Number(req.query['offset'] ?? 0);
const where = { tenantId };
if (instanceId)
where['instanceId'] = instanceId;
if (blocked === 'true')
where['isBlocked'] = true;
if (blocked === 'false')
where['isBlocked'] = false;
if (search) {
where['OR'] = [
{ name: { contains: search, mode: 'insensitive' } },
{ verifiedName: { contains: search, mode: 'insensitive' } },
{ notify: { contains: search, mode: 'insensitive' } },
{ phone: { contains: search } },
{ jid: { contains: search } },
];
}
const [total, contacts] = await Promise.all([
prisma_1.prisma.contact.count({ where }),
prisma_1.prisma.contact.findMany({
where,
// Tiebreaker por id é obrigatório: muitos contatos compartilham o mesmo
// updatedAt (bulk sync), e sem segunda coluna a paginação por OFFSET
// devolve linhas sobrepostas entre páginas — quebra "carregar mais".
orderBy: [{ updatedAt: 'desc' }, { id: 'asc' }],
take: limit,
skip: offset,
select: {
id: true,
instanceId: true,
jid: true,
name: true,
verifiedName: true,
notify: true,
phone: true,
avatarUrl: true,
scoreReputacao: true,
flagRestricao: true,
isBlocked: true,
createdAt: true,
updatedAt: true,
},
}),
]);
res.json({
total,
contacts: contacts.map(c => ({
...c,
displayName: c.name || c.verifiedName || c.notify || (c.phone ? `+${c.phone}` : (0, whatsapp_1.normalizeJid)(c.jid).split('@')[0])
}))
});
});
// ── GET /api/contacts/:id — detalhe ──────────────────────────────────────
router.get('/:id', async (req, res) => {
const tenantId = req.tenantId;
const id = req.params['id'];
const contact = await prisma_1.prisma.contact.findFirst({
where: { id, tenantId },
include: {
chats: {
orderBy: { lastMessageAt: 'desc' },
take: 1,
select: { id: true, unreadCount: true, lastMessageAt: true },
},
},
});
if (!contact) {
res.status(404).json({ error: 'Contato não encontrado' });
return;
}
res.json(contact);
});
// ── PATCH /api/contacts/:id — editar nome / bloquear ─────────────────────
router.patch('/:id', async (req, res) => {
const tenantId = req.tenantId;
const id = req.params['id'];
const schema = zod_1.z.object({
name: zod_1.z.string().min(1).max(255).optional(),
isBlocked: zod_1.z.boolean().optional(),
});
let body;
try {
body = schema.parse(req.body);
}
catch (err) {
res.status(400).json({ error: err.errors ?? err.message });
return;
}
const existing = await prisma_1.prisma.contact.findFirst({ where: { id, tenantId } });
if (!existing) {
res.status(404).json({ error: 'Contato não encontrado' });
return;
}
const updated = await prisma_1.prisma.contact.update({ where: { id }, data: body });
if (body.name && existing.instanceId) {
(0, chat_cache_1.invalidateChatListCache)(tenantId, existing.instanceId).catch(() => { });
}
res.json(updated);
});
// ── DELETE /api/contacts/:id ──────────────────────────────────────────────
router.delete('/:id', async (req, res) => {
const tenantId = req.tenantId;
const id = req.params['id'];
const existing = await prisma_1.prisma.contact.findFirst({ where: { id, tenantId } });
if (!existing) {
res.status(404).json({ error: 'Contato não encontrado' });
return;
}
// Desvincular chats antes de deletar o contato
await prisma_1.prisma.chat.updateMany({
where: { contactId: id },
data: { contactId: null },
});
await prisma_1.prisma.contact.delete({ where: { id } });
if (existing.instanceId) {
(0, chat_cache_1.invalidateChatListCache)(tenantId, existing.instanceId).catch(() => { });
}
res.status(204).send();
});
return router;
}