From 92113521e5ed0f7c8751dfeea8e96346b5b0fe74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=9D=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Fri, 9 Jan 2026 15:50:00 +0300 Subject: [PATCH] chore: Remove backup file --- backend/communications/api_views.py.backup | 764 --------------------- 1 file changed, 764 deletions(-) delete mode 100644 backend/communications/api_views.py.backup diff --git a/backend/communications/api_views.py.backup b/backend/communications/api_views.py.backup deleted file mode 100644 index 95ce5434..00000000 --- a/backend/communications/api_views.py.backup +++ /dev/null @@ -1,764 +0,0 @@ -# communications/api_views.py -"""API для расширенных функций мессенджера""" - -from django.contrib.auth.decorators import login_required -from django.db import models, transaction -from django.http import JsonResponse -from django.shortcuts import get_object_or_404 -from django.utils import timezone -from django.views.decorators.http import require_POST - -from channels.layers import get_channel_layer -from asgiref.sync import async_to_sync - -from .models import ( - Chat, - ChatMembership, - ChatUserSettings, - ForwardedMessage, - Message, - MessageAttachment, - MessageReply, -) - - -# ============ CHAT MANAGEMENT ============ - -@login_required -@require_POST -def create_chat(request): - """ - Создание нового чата - (group, department, channel, announcement, global) - """ - import json - - # Читаем данные из FormData или JSON - if request.content_type and 'multipart/form-data' in request.content_type: - # FormData (с файлами) - chat_type = request.POST.get('type', 'group') - name = request.POST.get('name', '').strip() - description = request.POST.get('description', '').strip() - department_id = request.POST.get('department_id') - include_all = request.POST.get('include_all_employees') == 'true' - is_main = request.POST.get('is_main') == 'true' - avatar = request.FILES.get('avatar') - participant_ids = request.POST.getlist('participant_ids') - else: - # JSON (без файлов - legacy) - try: - data = json.loads(request.body) - except json.JSONDecodeError: - return JsonResponse( - {'ok': False, 'error': 'Invalid JSON'}, status=400 - ) - - chat_type = data.get('type', 'group') - name = data.get('name', '').strip() - description = data.get('description', '').strip() - department_id = data.get('department_id') - include_all = data.get('include_all_employees', False) - is_main = data.get('is_main', False) - avatar = None - participant_ids = data.get('participant_ids', []) - - # Валидация типа чата - valid_types = ['group', 'department', 'channel', 'announcement', 'global'] - if chat_type not in valid_types: - types_str = ", ".join(valid_types) - return JsonResponse( - { - 'ok': False, - 'error': f'Invalid chat type. Must be one of: {types_str}' - }, - status=400 - ) - - # Валидация для announcement - только 1 на пользователя - if chat_type == 'announcement': - existing = Chat.objects.filter( - type='announcement', - created_by=request.user - ).first() - if existing: - if existing.is_blocked: - return JsonResponse( - { - 'ok': False, - 'error': 'Ваш чат объявлений заблокирован администратором' - }, - status=403 - ) - else: - return JsonResponse( - { - 'ok': False, - 'error': 'У вас уже есть чат объявлений', - 'existing_chat_id': existing.id - }, - status=400 - ) - - # Валидация названия - if not name and chat_type != 'private': - # Для announcement генерируем название автоматически - if chat_type == 'announcement': - user_name = request.user.get_full_name() or request.user.username - name = f"Объявления от {user_name}" - else: - return JsonResponse( - {'ok': False, 'error': 'Name is required'}, status=400 - ) - - # Валидация для чата отдела - if chat_type == 'department': - if not department_id: - return JsonResponse( - { - 'ok': False, - 'error': 'Department is required for department chat' - }, - status=400 - ) - try: - from employees.models import Department - department = Department.objects.get(id=department_id) - except Department.DoesNotExist: - return JsonResponse( - {'ok': False, 'error': 'Department not found'}, - status=400 - ) - else: - department = None - - # Валидация для основного чата - if is_main: - if chat_type == 'global': - # Проверяем, нет ли уже основного глобального чата - if Chat.objects.filter(type='global', is_main=True).exists(): - return JsonResponse( - {'ok': False, 'error': 'Main global chat already exists'}, - status=400 - ) - elif chat_type == 'department' and department: - # Проверяем, нет ли уже основного чата для этого отдела - exists = Chat.objects.filter( - type='department', - department=department, - is_main=True - ).exists() - if exists: - return JsonResponse( - { - 'ok': False, - 'error': 'Main department chat already exists' - }, - status=400 - ) - - # Создаем чат - chat = Chat.objects.create( - type=chat_type, - name=name, - description=description, - created_by=request.user, - department=department, - include_all_employees=include_all, - is_main=is_main, - can_reply=(chat_type != 'announcement') # Для announcement нельзя - ) - - # Добавляем аватар если был загружен - if avatar: - chat.avatar = avatar - chat.save() # Создатель - владелец с полными правами - ChatMembership.objects.create( - chat=chat, - user=request.user, - role='owner', - can_send_messages=True, - can_add_members=True, - can_remove_members=True, - can_pin_messages=True - ) - - # Добавляем участников для группового чата - if chat_type == 'group' and participant_ids: - from employees.models import Employee - for emp_id in participant_ids: - try: - employee = Employee.objects.get(id=emp_id) - if employee != request.user: # Создателя уже добавили - ChatMembership.objects.create( - chat=chat, - user=employee, - role='member', - can_send_messages=True - ) - except Employee.DoesNotExist: - pass - - # Для чатов с include_all_employees - добавляем всех активных - if include_all: - from employees.models import Employee - all_employees = Employee.objects.filter(is_active=True).exclude( - id=request.user.id - ) - for employee in all_employees: - # Для announcement и channel - обычные участники не могут писать - if chat_type == 'announcement': - can_send = False # Только создатель может писать - elif chat_type == 'channel': - can_send = False # Только модераторы могут писать - else: - can_send = True - - ChatMembership.objects.get_or_create( - chat=chat, - user=employee, - defaults={ - 'role': 'member', - 'can_send_messages': can_send - } - ) - - # Для чата отдела - добавляем всех сотрудников отдела - if chat_type == 'department' and department: - from employees.models import EmployeeDepartment - dept_employees = EmployeeDepartment.objects.filter( - department=department, - is_active=True - ).select_related('employee') - - for ed in dept_employees: - if ed.employee != request.user: # Создателя уже добавили - ChatMembership.objects.get_or_create( - chat=chat, - user=ed.employee, - defaults={ - 'role': 'member', - 'can_send_messages': True - } - ) - - return JsonResponse({ - 'ok': True, - 'chat_id': chat.id, - 'chat_type': chat.type, - 'name': chat.name - }) - - -@login_required -@require_POST -def block_announcement(request, chat_id): - """ - Блокирует announcement чат (только для staff/superuser) - После блокировки чат скрывается у всех пользователей - """ - if not (request.user.is_staff or request.user.is_superuser): - return JsonResponse( - {'ok': False, 'error': 'Permission denied'}, status=403 - ) - - chat = get_object_or_404(Chat, pk=chat_id, type='announcement') - - if chat.is_blocked: - return JsonResponse( - {'ok': False, 'error': 'Chat already blocked'}, status=400 - ) - - chat.is_blocked = True - chat.blocked_at = timezone.now() - chat.blocked_by = request.user - chat.save() - - return JsonResponse({ - 'ok': True, - 'message': f'Announcement "{chat.name}" blocked' - }) - - -@login_required -@require_POST -def unblock_announcement(request, chat_id): - """ - Разблокирует announcement чат (только для staff/superuser) - """ - if not (request.user.is_staff or request.user.is_superuser): - return JsonResponse( - {'ok': False, 'error': 'Permission denied'}, status=403 - ) - - chat = get_object_or_404(Chat, pk=chat_id, type='announcement') - - if not chat.is_blocked: - return JsonResponse( - {'ok': False, 'error': 'Chat is not blocked'}, status=400 - ) - - chat.is_blocked = False - chat.blocked_at = None - chat.blocked_by = None - chat.save() - - return JsonResponse({ - 'ok': True, - 'message': f'Announcement "{chat.name}" unblocked' - }) - - -@login_required -@require_POST -def update_chat(request, chat_id): - """Обновление названия/описания/аватара чата""" - chat = get_object_or_404(Chat, pk=chat_id) - - # Проверка прав (только owner/admin) - membership = ChatMembership.objects.filter( - chat=chat, - user=request.user, - role__in=['owner', 'admin'] - ).first() - - if not membership: - return JsonResponse({'ok': False, 'error': 'Forbidden'}, status=403) - - # Обновление полей - if 'name' in request.POST: - chat.name = request.POST['name'].strip() - if 'description' in request.POST: - chat.description = request.POST['description'].strip() - if 'avatar' in request.FILES: - chat.avatar = request.FILES['avatar'] - - chat.save() - - return JsonResponse({'ok': True}) - - -# ============ CHAT USER SETTINGS ============ - -@login_required -@require_POST -def pin_chat(request, chat_id): - """Закрепление/открепление чата""" - chat = get_object_or_404(Chat, pk=chat_id) - is_pinned = request.POST.get('pinned', 'true').lower() == 'true' - - settings, created = ChatUserSettings.objects.get_or_create( - chat=chat, - user=request.user - ) - - settings.is_pinned = is_pinned - if is_pinned: - settings.pinned_at = timezone.now() - # Определяем порядок - max_order = ChatUserSettings.objects.filter( - user=request.user, - is_pinned=True - ).aggregate(max_order=models.Max('pin_order'))['max_order'] or 0 - settings.pin_order = max_order + 1 - else: - settings.pinned_at = None - settings.pin_order = 0 - - settings.save() - - return JsonResponse({'ok': True, 'pinned': is_pinned}) - - -@login_required -@require_POST -def set_chat_notifications(request, chat_id): - """Включение/выключение уведомлений""" - chat = get_object_or_404(Chat, pk=chat_id) - enabled = request.POST.get('enabled', 'true').lower() == 'true' - - settings, created = ChatUserSettings.objects.get_or_create( - chat=chat, - user=request.user - ) - settings.notifications_enabled = enabled - settings.save() - - return JsonResponse({'ok': True, 'enabled': enabled}) - - -# ============ MESSAGE ACTIONS ============ - -@login_required -@require_POST -def edit_message(request, message_id): - """Редактирование сообщения""" - message = get_object_or_404(Message, pk=message_id) - - if message.author != request.user: - return JsonResponse({'ok': False, 'error': 'Forbidden'}, status=403) - - new_content = request.POST.get('content', '').strip() - if not new_content: - return JsonResponse({'ok': False, 'error': 'Empty content'}, status=400) - - # Сохраняем историю - if not message.is_edited: - message.edit_history = [] - - message.edit_history.append({ - 'timestamp': timezone.now().isoformat(), - 'old_content': message.content - }) - - message.content = new_content - message.is_edited = True - message.edited_at = timezone.now() - message.save() - - return JsonResponse({'ok': True, 'edited_at': message.edited_at.isoformat()}) - - -@login_required -@require_POST -def delete_message(request, message_id): - """Удаление сообщения""" - import logging - logger = logging.getLogger(__name__) - - logger.info( - "[DELETE_MESSAGE] API called: message_id=%s, user=%s", - message_id, request.user.username - ) - - message = get_object_or_404(Message, pk=message_id) - logger.info( - "[DELETE_MESSAGE] Message found: chat_id=%s, author=%s", - message.chat_id, message.author.username - ) - - # Проверка: автор или модератор/админ чата - can_delete = False - if message.author == request.user: - can_delete = True - logger.info("[DELETE_MESSAGE] User is author, can delete") - else: - membership = ChatMembership.objects.filter( - chat=message.chat, - user=request.user, - role__in=['owner', 'admin', 'moderator'] - ).first() - if membership: - can_delete = True - logger.info( - "[DELETE_MESSAGE] User has moderator role: %s", - membership.role - ) - - if not can_delete: - logger.warning( - "[DELETE_MESSAGE] Permission denied for user %s", - request.user.username - ) - return JsonResponse({'ok': False, 'error': 'Forbidden'}, status=403) - - logger.info("[DELETE_MESSAGE] Marking message as deleted") - message.is_deleted = True - message.deleted_at = timezone.now() - message.deleted_by = request.user - message.save() - logger.info( - "[DELETE_MESSAGE] Message deleted successfully: %s", message_id - ) - - # Отправляем WebSocket событие всем участникам чата - logger.info( - "[DELETE_MESSAGE] Sending WebSocket event to chat group: chat_%s", - message.chat_id - ) - channel_layer = get_channel_layer() - async_to_sync(channel_layer.group_send)( - f"chat_{message.chat_id}", - { - "type": "chat.message_deleted", - "chat_id": message.chat_id, - "message_id": message_id - } - ) - logger.info( - "[DELETE_MESSAGE] WebSocket event sent for message_id=%s", message_id - ) - - return JsonResponse({'ok': True}) - - -@login_required -@require_POST -def pin_message(request, message_id): - """Закрепление сообщения в чате""" - message = get_object_or_404(Message, pk=message_id) - - # Проверка прав - membership = ChatMembership.objects.filter( - chat=message.chat, - user=request.user, - can_pin_messages=True - ).first() - - if not membership: - return JsonResponse({'ok': False, 'error': 'Forbidden'}, status=403) - - is_pinned = request.POST.get('pinned', 'true').lower() == 'true' - - message.is_pinned = is_pinned - if is_pinned: - message.pinned_by = request.user - message.pinned_at = timezone.now() - else: - message.pinned_by = None - message.pinned_at = None - - message.save() - - return JsonResponse({'ok': True, 'pinned': is_pinned}) - - -# ============ ATTACHMENTS ============ -# Примечание: endpoint для загрузки сообщений с файлами -# перенесён в api/v1/communications/views.py - -@login_required -@require_POST -def upload_attachment(request): - """Загрузка вложения к существующему сообщению""" - if 'file' not in request.FILES: - return JsonResponse({'ok': False, 'error': 'No file'}, status=400) - - message_id = request.POST.get('message_id') - if not message_id: - return JsonResponse( - {'ok': False, 'error': 'message_id required'}, - status=400 - ) - - message = get_object_or_404(Message, pk=message_id) - - if message.author != request.user: - return JsonResponse({'ok': False, 'error': 'Forbidden'}, status=403) - - uploaded_file = request.FILES['file'] - - # Определение типа файла - mime_type = uploaded_file.content_type or '' - if mime_type.startswith('image/'): - file_type = 'image' - elif mime_type.startswith('video/'): - file_type = 'video' - elif mime_type.startswith('audio/'): - file_type = 'audio' - else: - file_type = 'file' - - attachment = MessageAttachment.objects.create( - message=message, - file=uploaded_file, - file_type=file_type, - file_name=uploaded_file.name, - file_size=uploaded_file.size, - mime_type=mime_type - ) - - message.has_attachments = True - message.save() - - return JsonResponse({ - 'ok': True, - 'attachment_id': attachment.id, - 'file_name': attachment.file_name, - 'file_type': attachment.file_type, - 'file_url': attachment.file.url - }) - - -# ============ FORWARDING ============ - -@login_required -@require_POST -def forward_message(request, message_id): - """Пересылка сообщения в другой чат""" - original = get_object_or_404(Message, pk=message_id) - target_chat_id = request.POST.get('target_chat_id') - - if not target_chat_id: - return JsonResponse({'ok': False, 'error': 'target_chat_id required'}, status=400) - - target_chat = get_object_or_404(Chat, pk=target_chat_id) - - # Проверка доступа к целевому чату - membership = ChatMembership.objects.filter( - chat=target_chat, - user=request.user, - can_send_messages=True - ).first() - - if not membership: - return JsonResponse({'ok': False, 'error': 'Cannot send to this chat'}, status=403) - - with transaction.atomic(): - # Создаем новое сообщение - forwarded = Message.objects.create( - chat=target_chat, - author=request.user, - content=original.content, - is_forwarded=True - ) - - # Определяем оригинальное сообщение в цепочке - if hasattr(original, 'forward_info'): - original_message = original.forward_info.original_message - forward_count = original.forward_info.forward_count + 1 - else: - original_message = original - forward_count = 1 - - # Создаем информацию о пересылке - ForwardedMessage.objects.create( - message=forwarded, - original_message=original_message, - immediate_source=original, - original_chat=original.chat, - original_author=original.author, - forwarded_by=request.user, - forward_count=forward_count, - preserved_content=original.content, - preserved_author_name=original.author.get_full_name() - ) - - return JsonResponse({'ok': True, 'new_message_id': forwarded.id}) - - -# ============ REPLIES ============ - -@login_required -@require_POST -def reply_to_message(request, message_id): - """Ответ на сообщение""" - replied_to = get_object_or_404(Message, pk=message_id) - content = request.POST.get('content', '').strip() - reply_type = request.POST.get('reply_type', 'inline') - - if not content: - return JsonResponse({'ok': False, 'error': 'Content required'}, status=400) - - # Создаем сообщение-ответ - reply = Message.objects.create( - chat=replied_to.chat, - author=request.user, - content=content, - reply_to=replied_to - ) - - # Создаем расширенную информацию об ответе - MessageReply.objects.create( - message=reply, - replied_to=replied_to, - is_cross_chat_reply=False, - preserved_text=replied_to.content[:200], - preserved_author_name=replied_to.author.get_full_name(), - reply_type=reply_type - ) - - # Если это тред, обновляем счетчик - if reply_type == 'thread': - if replied_to.thread_root: - root = replied_to.thread_root - else: - root = replied_to - - reply.thread_root = root - reply.save() - - root.thread_reply_count += 1 - root.save() - - return JsonResponse({ - 'ok': True, - 'reply_id': reply.id, - 'replied_to_id': replied_to.id - }) - - -# ============ CHAT MEMBERSHIP ============ - -@login_required -@require_POST -def add_member(request, chat_id): - """Добавление участника в чат""" - chat = get_object_or_404(Chat, pk=chat_id) - user_id = request.POST.get('user_id') - - if not user_id: - return JsonResponse({'ok': False, 'error': 'user_id required'}, status=400) - - # Проверка прав - membership = ChatMembership.objects.filter( - chat=chat, - user=request.user, - can_add_members=True - ).first() - - if not membership: - return JsonResponse({'ok': False, 'error': 'Forbidden'}, status=403) - - from employees.models import Employee - new_user = get_object_or_404(Employee, pk=user_id) - - # Создаем или активируем участие - new_membership, created = ChatMembership.objects.get_or_create( - chat=chat, - user=new_user, - defaults={ - 'role': 'member', - 'invited_by': request.user, - 'can_send_messages': True - } - ) - - if not created and not new_membership.is_active: - new_membership.is_active = True - new_membership.left_at = None - new_membership.save() - - return JsonResponse({'ok': True, 'created': created}) - - -@login_required -@require_POST -def remove_member(request, chat_id): - """Удаление участника из чата""" - chat = get_object_or_404(Chat, pk=chat_id) - user_id = request.POST.get('user_id') - - if not user_id: - return JsonResponse({'ok': False, 'error': 'user_id required'}, status=400) - - # Проверка прав - membership = ChatMembership.objects.filter( - chat=chat, - user=request.user, - can_remove_members=True - ).first() - - if not membership: - return JsonResponse({'ok': False, 'error': 'Forbidden'}, status=403) - - target_membership = ChatMembership.objects.filter( - chat=chat, - user_id=user_id - ).first() - - if target_membership: - target_membership.is_active = False - target_membership.left_at = timezone.now() - target_membership.save() - - return JsonResponse({'ok': True})