117 lines
4.5 KiB
Python
117 lines
4.5 KiB
Python
"""
|
||
Сервисы для системы чата.
|
||
Централизованная логика создания и управления чатами.
|
||
"""
|
||
from django.db import transaction
|
||
from django.db.models import Q
|
||
from .models import Chat, ChatParticipant
|
||
|
||
|
||
class ChatService:
|
||
"""
|
||
Сервис для работы с чатами.
|
||
Обеспечивает атомарность операций и предотвращает создание дубликатов.
|
||
"""
|
||
|
||
@staticmethod
|
||
def get_or_create_direct_chat(user1, user2, created_by=None):
|
||
"""
|
||
Получить или создать личный чат между двумя пользователями.
|
||
|
||
Использует блокировку для предотвращения race condition
|
||
при одновременных запросах на создание чата.
|
||
|
||
Args:
|
||
user1: Первый пользователь (User)
|
||
user2: Второй пользователь (User)
|
||
created_by: Кто создает чат (по умолчанию user1)
|
||
|
||
Returns:
|
||
tuple: (chat, created) - объект чата и флаг создания
|
||
"""
|
||
if user1.id == user2.id:
|
||
raise ValueError("Нельзя создать чат с самим собой")
|
||
|
||
# Нормализуем порядок пользователей для консистентного поиска
|
||
users = sorted([user1, user2], key=lambda u: u.id)
|
||
|
||
with transaction.atomic():
|
||
# Ищем существующий чат между пользователями
|
||
# select_for_update() несовместим с distinct() в PostgreSQL,
|
||
# поэтому сначала ищем без блокировки, затем лочим найденный объект
|
||
existing_chat = Chat.objects.filter(
|
||
chat_type='direct',
|
||
participants__user=users[0]
|
||
).filter(
|
||
participants__user=users[1]
|
||
).distinct().first()
|
||
|
||
if existing_chat:
|
||
# Лочим конкретную запись для безопасного возврата
|
||
existing_chat = Chat.objects.select_for_update().get(pk=existing_chat.pk)
|
||
|
||
if existing_chat:
|
||
return existing_chat, False
|
||
|
||
# Чата нет - создаем новый
|
||
creator = created_by or users[0]
|
||
chat = Chat.objects.create(
|
||
chat_type='direct',
|
||
created_by=creator
|
||
)
|
||
|
||
# Определяем роли участников
|
||
# Создатель становится админом
|
||
ChatParticipant.objects.create(
|
||
chat=chat,
|
||
user=users[0],
|
||
role='admin' if users[0] == creator else 'member'
|
||
)
|
||
|
||
ChatParticipant.objects.create(
|
||
chat=chat,
|
||
user=users[1],
|
||
role='admin' if users[1] == creator else 'member'
|
||
)
|
||
|
||
return chat, True
|
||
|
||
@staticmethod
|
||
def get_direct_chat(user1, user2):
|
||
"""
|
||
Получить существующий личный чат между двумя пользователями.
|
||
|
||
Args:
|
||
user1: Первый пользователь
|
||
user2: Второй пользователь
|
||
|
||
Returns:
|
||
Chat или None
|
||
"""
|
||
return Chat.objects.filter(
|
||
chat_type='direct',
|
||
participants__user=user1
|
||
).filter(
|
||
participants__user=user2
|
||
).distinct().first()
|
||
|
||
@staticmethod
|
||
def ensure_participant(chat, user, role='member'):
|
||
"""
|
||
Убедиться что пользователь является участником чата.
|
||
Если нет - добавить его.
|
||
|
||
Args:
|
||
chat: Чат
|
||
user: Пользователь
|
||
role: Роль (по умолчанию 'member')
|
||
|
||
Returns:
|
||
tuple: (participant, created)
|
||
"""
|
||
return ChatParticipant.objects.get_or_create(
|
||
chat=chat,
|
||
user=user,
|
||
defaults={'role': role}
|
||
)
|