Files
mercado.clube67.com/backend/controllers/directMessageController.js
T

233 lines
7.3 KiB
JavaScript

'use strict'
const DirectMessage = require('../models/DirectMessage')
const { query: queryDb } = require('../database/postgres')
const { sseSend, isOnline } = require('../services/sse')
/**
* Extrai accountId e tenantId da request, compatível com:
* - verifyTeamToken → req.teamAccount = { accountId, tenantId, ... }
* - verifyAnyToken → req.admin = { id, tenant_id, ... } OU req.teamAccount
*/
function extractAuth(req) {
const accountId = req.teamAccount?.accountId || req.admin?.id || null
const tenantId = req.tenantId || req.teamAccount?.tenantId || req.admin?.tenant_id || null
return { accountId, tenantId }
}
class DirectMessageController {
/**
* POST /api/admin/inbox/direct-message
* Enviar DM para um colaborador
*/
async send(req, res) {
try {
const { to_account, content } = req.body
const { accountId: fromAccountId, tenantId } = extractAuth(req)
if (!fromAccountId || !tenantId) {
return res.status(401).json({ error: 'Não autenticado' })
}
if (!to_account || !content?.trim()) {
return res.status(400).json({ error: 'to_account e content obrigatórios' })
}
const msg = await DirectMessage.send(tenantId, fromAccountId, to_account, content)
res.json(msg)
// Notificar destinatário via SSE (se estiver conectado)
sseSend(to_account, 'dm.new', {
id: msg.id,
from: fromAccountId,
to: to_account,
content: content,
created_at: msg.created_at,
})
} catch (err) {
console.error('[dm.send]', err.message)
res.status(500).json({ error: 'Erro ao enviar mensagem' })
}
}
/**
* GET /api/admin/inbox/direct/:otherAccountId
* Histórico de DMs com um colaborador
*/
async getConversation(req, res) {
try {
const { otherAccountId } = req.params
const { accountId, tenantId } = extractAuth(req)
const limit = Math.min(Number(req.query.limit) || 50, 200)
if (!accountId || !tenantId) {
return res.status(401).json({ error: 'Não autenticado' })
}
if (!otherAccountId) {
return res.status(400).json({ error: 'otherAccountId obrigatório' })
}
const msgs = await DirectMessage.getConversation(tenantId, accountId, Number(otherAccountId), limit)
// Marcar como lidas ao abrir a conversa
await DirectMessage.markAsRead(tenantId, accountId, Number(otherAccountId))
res.json(msgs)
} catch (err) {
console.error('[dm.getConversation]', err.message)
res.status(500).json({ error: 'Erro ao buscar conversa' })
}
}
/**
* GET /api/admin/inbox/direct-messages
* Últimas DMs com cada colaborador + contagem de não lidas
*/
async getLastMessages(req, res) {
try {
const { accountId, tenantId } = extractAuth(req)
if (!accountId || !tenantId) {
return res.status(401).json({ error: 'Não autenticado' })
}
const [lastMsgs, unreadByContact] = await Promise.all([
DirectMessage.getLastMessagesByAccount(tenantId, accountId),
DirectMessage.getUnreadCountByContact(tenantId, accountId),
])
// Enriquecer com info do outro usuário + unread count
const enriched = await Promise.all(
lastMsgs.map(async (msg) => {
const otherAccountId = msg.other_account
const rows = await queryDb(
`SELECT id, name, email, availability FROM accounts WHERE id = $1`,
[otherAccountId]
)
const account = rows[0] || null
return {
id: msg.id,
other_account: otherAccountId,
other_account_name: account?.name || 'Unknown',
other_account_availability: account?.availability || 'offline',
last_message: msg.content,
last_message_at: msg.created_at,
unread_count: unreadByContact[otherAccountId] || 0,
}
})
)
res.json(enriched)
} catch (err) {
console.error('[dm.getLastMessages]', err.message)
res.status(500).json({ error: 'Erro ao buscar DMs' })
}
}
/**
* GET /api/admin/inbox/colleagues
* Listar todos os colaboradores do tenant com status online/offline
* Para popular a coluna de contatos
*/
async getColleagues(req, res) {
try {
const { accountId, tenantId } = extractAuth(req)
if (!tenantId) {
return res.status(401).json({ error: 'Não autenticado' })
}
// accountId pode ser null para admins sem account — nesse caso lista todos
const colleagues = accountId
? await queryDb(
`SELECT a.id, a.name, a.email, a.availability, a.avatar_url
FROM accounts a
WHERE a.tenant_id = $1
AND a.id != $2
AND a.active = true
ORDER BY
CASE WHEN a.availability = 'online' THEN 0
WHEN a.availability = 'busy' THEN 1
ELSE 2
END,
a.name`,
[tenantId, accountId]
)
: await queryDb(
`SELECT a.id, a.name, a.email, a.availability, a.avatar_url
FROM accounts a
WHERE a.tenant_id = $1
AND a.active = true
ORDER BY
CASE WHEN a.availability = 'online' THEN 0
WHEN a.availability = 'busy' THEN 1
ELSE 2
END,
a.name`,
[tenantId]
)
// Adicionar unread count para cada colega (só quando temos accountId)
const unreadByContact = accountId
? await DirectMessage.getUnreadCountByContact(tenantId, accountId)
: {}
// Presença em tempo real: SSE conectado = online (exceto se DB diz busy)
const enriched = colleagues.map(c => {
let availability = c.availability
if (isOnline(c.id)) {
// Se conectado via SSE e não está como busy no DB → online
if (availability !== 'busy') availability = 'online'
} else {
// Sem conexão SSE → offline independente do DB
availability = 'offline'
}
return { ...c, availability, unread_count: unreadByContact[c.id] || 0 }
})
res.json(enriched)
} catch (err) {
console.error('[dm.getColleagues]', err.message)
res.status(500).json({ error: 'Erro ao buscar colaboradores' })
}
}
/**
* PATCH /api/admin/inbox/direct/:messageId/read
* Marcar uma DM como lida
*/
async markAsRead(req, res) {
try {
const { messageId } = req.params
const { accountId } = extractAuth(req)
if (!accountId) {
return res.status(401).json({ error: 'Não autenticado' })
}
// Verificar que a mensagem é para este usuário
const rows = await queryDb(
`SELECT id FROM direct_messages WHERE id = $1 AND to_account = $2`,
[messageId, accountId]
)
const msg = rows[0] || null
if (!msg) {
return res.status(404).json({ error: 'Mensagem não encontrada' })
}
await queryDb(
`UPDATE direct_messages SET read_at = NOW() WHERE id = $1`,
[messageId]
)
res.json({ ok: true })
} catch (err) {
console.error('[dm.markAsRead]', err.message)
res.status(500).json({ error: 'Erro ao marcar como lida' })
}
}
}
module.exports = new DirectMessageController()