Files
SKEEN-Proyecto/odoo-addons/skeen_chat/controllers/chat.py

166 lines
7.4 KiB
Python

# -*- coding: utf-8 -*-
"""Chat interno 1-a-1 entre usuarios del frontend React (skeen.frontend.user).
Mismo esquema de auth HMAC que el resto de la API. Todos los endpoints están
restringidos al usuario autenticado: solo puede listar/leer conversaciones
donde él es sender o recipient, y solo puede enviar como él mismo.
"""
from odoo import http
from odoo.http import request
from odoo.addons.skeen_whatsapp.controllers.auth import require_role
from odoo.addons.skeen_whatsapp.controllers.frontend import json_response, _parse_json_body
MAX_BODY = 2000
HISTORY_LIMIT = 50
LIST_SCAN_LIMIT = 500
class SkeenChatController(http.Controller):
def _message_to_dict(self, msg, me_id):
return {
'id': msg.id,
'sender_id': msg.sender_id.id,
'recipient_id': msg.recipient_id.id,
'body': msg.body,
'is_read': msg.is_read,
'mine': msg.sender_id.id == me_id,
'date': msg.create_date.strftime('%Y-%m-%d %H:%M:%S') if msg.create_date else None,
}
@http.route('/skeen/frontend/v1/chat/users', type='http', auth='none', methods=['POST', 'OPTIONS'], csrf=False)
@require_role('lectura')
def chat_users(self, **kw):
"""Usuarios activos (menos yo) con conteo de no leídos y último mensaje.
Orden: primero conversaciones con actividad (más reciente primero),
luego el resto alfabéticamente.
"""
try:
me = request.frontend_user
User = request.env['skeen.frontend.user'].sudo()
Msg = request.env['skeen.chat.message'].sudo()
users = User.search([('active', '=', True), ('id', '!=', me.id)], order='name')
unread = {}
for g in Msg.read_group(
[('recipient_id', '=', me.id), ('is_read', '=', False)],
['sender_id'], ['sender_id']):
unread[g['sender_id'][0]] = g['sender_id_count']
last = {}
recent = Msg.search([
'|', ('sender_id', '=', me.id), ('recipient_id', '=', me.id),
], order='id desc', limit=LIST_SCAN_LIMIT)
for m in recent:
other_id = m.recipient_id.id if m.sender_id.id == me.id else m.sender_id.id
if other_id not in last:
last[other_id] = {
'body': m.body or '',
'date': m.create_date.strftime('%Y-%m-%d %H:%M:%S') if m.create_date else None,
'mine': m.sender_id.id == me.id,
}
# Conversaciones con actividad primero (más reciente arriba)
conversations = sorted(
(u for u in users if u.id in last),
key=lambda u: last[u.id]['date'] or '', reverse=True)
rest = [u for u in users if u.id not in last]
return json_response({
'status': 'success',
'users': [{
'id': u.id,
'name': u.name,
'role': u.role,
'unread': unread.get(u.id, 0),
'last_message': last.get(u.id, {}).get('body'),
'last_date': last.get(u.id, {}).get('date'),
'last_mine': last.get(u.id, {}).get('mine'),
} for u in conversations + rest],
})
except Exception as e:
return json_response({'status': 'error', 'message': str(e)}, 500)
@http.route('/skeen/frontend/v1/chat/messages', type='http', auth='none', methods=['POST', 'OPTIONS'], csrf=False)
@require_role('lectura')
def chat_messages(self, **kw):
"""Historial con otro usuario (últimos 50; since_id para incremental).
Marca como leídos los mensajes recibidos de ese usuario.
"""
try:
me = request.frontend_user
data = _parse_json_body()
other_id = int(data.get('user_id') or 0)
if not other_id or other_id == me.id:
return json_response({'status': 'error', 'message': 'user_id inválido'}, 400)
User = request.env['skeen.frontend.user'].sudo()
if not User.search([('id', '=', other_id), ('active', '=', True)], limit=1):
return json_response({'status': 'error', 'message': 'Usuario no encontrado'}, 404)
domain = [
'|', '&', ('sender_id', '=', me.id), ('recipient_id', '=', other_id),
'&', ('sender_id', '=', other_id), ('recipient_id', '=', me.id),
]
since_id = int(data.get('since_id') or 0)
if since_id:
domain.append(('id', '>', since_id))
Msg = request.env['skeen.chat.message'].sudo()
msgs = Msg.search(domain, order='id desc', limit=HISTORY_LIMIT).sorted('id')
# Marcar leídos los recibidos en esta conversación
incoming = msgs.filtered(lambda m: m.recipient_id.id == me.id and not m.is_read)
if incoming:
incoming.write({'is_read': True})
return json_response({
'status': 'success',
'messages': [self._message_to_dict(m, me.id) for m in msgs],
})
except Exception as e:
return json_response({'status': 'error', 'message': str(e)}, 500)
@http.route('/skeen/frontend/v1/chat/send', type='http', auth='none', methods=['POST', 'OPTIONS'], csrf=False)
@require_role('lectura')
def chat_send(self, **kw):
"""Enviar mensaje directo a otro usuario."""
try:
me = request.frontend_user
data = _parse_json_body()
other_id = int(data.get('user_id') or 0)
body = (data.get('message') or '').strip()
if not other_id or other_id == me.id:
return json_response({'status': 'error', 'message': 'user_id inválido'}, 400)
if not body:
return json_response({'status': 'error', 'message': 'El mensaje no puede estar vacío'}, 400)
if len(body) > MAX_BODY:
return json_response(
{'status': 'error', 'message': f'El mensaje excede {MAX_BODY} caracteres'}, 400)
User = request.env['skeen.frontend.user'].sudo()
if not User.search([('id', '=', other_id), ('active', '=', True)], limit=1):
return json_response({'status': 'error', 'message': 'Usuario no encontrado'}, 404)
msg = request.env['skeen.chat.message'].sudo().create({
'sender_id': me.id,
'recipient_id': other_id,
'body': body,
})
return json_response({
'status': 'success',
'message': self._message_to_dict(msg, me.id),
}, 201)
except Exception as e:
return json_response({'status': 'error', 'message': str(e)}, 500)
@http.route('/skeen/frontend/v1/chat/unread_count', type='http', auth='none', methods=['POST', 'OPTIONS'], csrf=False)
@require_role('lectura')
def chat_unread_count(self, **kw):
"""Total global de mensajes no leídos del usuario (badge del menú)."""
try:
me = request.frontend_user
total = request.env['skeen.chat.message'].sudo().search_count([
('recipient_id', '=', me.id), ('is_read', '=', False),
])
return json_response({'status': 'success', 'unread': total})
except Exception as e:
return json_response({'status': 'error', 'message': str(e)}, 500)