""" Сервисы для системы чата. Централизованная логика создания и управления чатами. """ 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} )