233 lines
7.3 KiB
JavaScript
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()
|