diff --git a/backend/api/v1/communications/__init__.py b/backend/api/v1/communications/__init__.py new file mode 100644 index 00000000..c69fd753 --- /dev/null +++ b/backend/api/v1/communications/__init__.py @@ -0,0 +1 @@ +# backend/api/v1/communications/__init__.py diff --git a/backend/api/v1/communications/views.py b/backend/api/v1/communications/views.py new file mode 100644 index 00000000..2a7757d2 --- /dev/null +++ b/backend/api/v1/communications/views.py @@ -0,0 +1,113 @@ +# backend/api/v1/communications/views.py +"""API views для чатов и сообщений""" + +from django.contrib.auth.decorators import login_required +from django.db import transaction +from django.http import JsonResponse +from django.shortcuts import get_object_or_404 +from django.views.decorators.http import require_POST + +from communications.models import ( + Chat, + ChatMembership, + Message, + MessageAttachment, +) + + +@login_required +@require_POST +def upload_message_with_attachments(request): + """Отправка сообщения с вложениями""" + chat_id = request.POST.get('chat_id') + content = request.POST.get('content', '').strip() + + if not chat_id: + return JsonResponse( + {'ok': False, 'error': 'chat_id required'}, + status=400 + ) + + chat = get_object_or_404(Chat, pk=chat_id) + + # Проверка доступа к чату + membership = ChatMembership.objects.filter( + chat=chat, + user=request.user, + is_active=True, + can_send_messages=True + ).first() + + if not membership: + return JsonResponse( + {'ok': False, 'error': 'Cannot send messages to this chat'}, + status=403 + ) + + # Если нет ни текста, ни файлов + if not content and not request.FILES: + return JsonResponse( + {'ok': False, 'error': 'Content or files required'}, + status=400 + ) + + with transaction.atomic(): + # Создаем сообщение + message = Message.objects.create( + chat=chat, + author=request.user, + content=content or '', + has_attachments=bool(request.FILES) + ) + + # Обрабатываем все загруженные файлы + attachments = [] + for file_key in request.FILES: + uploaded_file = request.FILES[file_key] + + # Определение типа файла + 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 + ) + + attachments.append({ + 'id': attachment.id, + 'file_name': attachment.file_name, + 'file_type': attachment.file_type, + 'file_url': attachment.file.url, + 'file_size': attachment.file_size, + 'mime_type': attachment.mime_type + }) + + avatar_url = None + if hasattr(request.user, 'avatar') and request.user.avatar: + avatar_url = request.user.avatar.url + + return JsonResponse({ + 'ok': True, + 'message_id': message.id, + 'chat_id': chat.id, + 'author': { + 'id': request.user.id, + 'full_name': request.user.get_full_name(), + 'avatar': avatar_url + }, + 'content': message.content, + 'created_at': message.created_at.isoformat(), + 'attachments': attachments + }) diff --git a/backend/api/v1/urls.py b/backend/api/v1/urls.py index 1298c37c..8ed734d0 100644 --- a/backend/api/v1/urls.py +++ b/backend/api/v1/urls.py @@ -10,6 +10,7 @@ from .employees.views import (DepartmentRoleViewSet, DepartmentViewSet, from .feed.views import CommentViewSet, PostViewSet from .documents.views import DocumentViewSet from .requests_app.views import RequestViewSet +from .communications.views import upload_message_with_attachments app_name = "v1" @@ -36,5 +37,10 @@ urlpatterns = [ path("auth/register/", RegisterAPIView.as_view(), name="register"), path("auth/resend-email/", ResendEmailAPIView.as_view(), name="resend-email"), path("auth/verify-email/", VerifyEmailAPIView.as_view(), name="verify-email"), + path( + "communications/upload-message/", + upload_message_with_attachments, + name="upload_message" + ), path("", include(router.urls)), ] diff --git a/backend/communications/api_views.py b/backend/communications/api_views.py index 4e0d0772..13dc24d5 100644 --- a/backend/communications/api_views.py +++ b/backend/communications/api_views.py @@ -306,104 +306,8 @@ def pin_message(request, message_id): # ============ ATTACHMENTS ============ - -@login_required -@require_POST -def upload_message_with_attachments(request): - """Отправка сообщения с вложениями""" - chat_id = request.POST.get('chat_id') - content = request.POST.get('content', '').strip() - - if not chat_id: - return JsonResponse( - {'ok': False, 'error': 'chat_id required'}, - status=400 - ) - - chat = get_object_or_404(Chat, pk=chat_id) - - # Проверка доступа к чату - membership = ChatMembership.objects.filter( - chat=chat, - user=request.user, - is_active=True, - can_send_messages=True - ).first() - - if not membership: - return JsonResponse( - {'ok': False, 'error': 'Cannot send messages to this chat'}, - status=403 - ) - - # Если нет ни текста, ни файлов - if not content and not request.FILES: - return JsonResponse( - {'ok': False, 'error': 'Content or files required'}, - status=400 - ) - - with transaction.atomic(): - # Создаем сообщение - message = Message.objects.create( - chat=chat, - author=request.user, - content=content or '', - has_attachments=bool(request.FILES) - ) - - # Обрабатываем все загруженные файлы - attachments = [] - for file_key in request.FILES: - uploaded_file = request.FILES[file_key] - - # Определение типа файла - 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 - ) - - attachments.append({ - 'id': attachment.id, - 'file_name': attachment.file_name, - 'file_type': attachment.file_type, - 'file_url': attachment.file.url, - 'file_size': attachment.file_size, - 'mime_type': attachment.mime_type - }) - - avatar_url = None - if hasattr(request.user, 'avatar') and request.user.avatar: - avatar_url = request.user.avatar.url - - return JsonResponse({ - 'ok': True, - 'message_id': message.id, - 'chat_id': chat.id, - 'author': { - 'id': request.user.id, - 'full_name': request.user.get_full_name(), - 'avatar': avatar_url - }, - 'content': message.content, - 'created_at': message.created_at.isoformat(), - 'attachments': attachments - }) - +# Примечание: endpoint для загрузки сообщений с файлами +# перенесён в api/v1/communications/views.py @login_required @require_POST diff --git a/backend/communications/urls.py b/backend/communications/urls.py index 3d01c05f..6caffdc2 100644 --- a/backend/communications/urls.py +++ b/backend/communications/urls.py @@ -69,11 +69,7 @@ urlpatterns = [ ), # Attachments - path( - "api/upload-message/", - api_views.upload_message_with_attachments, - name="api_upload_message" - ), + # Основной endpoint для загрузки сообщений с файлами находится в api/v1 path( "api/attachment/upload/", api_views.upload_attachment, diff --git a/backend/templates/communications/chat_detail.html b/backend/templates/communications/chat_detail.html index af55e7e1..04f2f4b7 100644 --- a/backend/templates/communications/chat_detail.html +++ b/backend/templates/communications/chat_detail.html @@ -344,7 +344,7 @@ document.addEventListener('DOMContentLoaded', function() { formData.append('chat_id', '{{ chat.id }}'); try { - const response = await fetch('/communications/api/upload-message/', { + const response = await fetch('/api/v1/communications/upload-message/', { method: 'POST', body: formData, headers: {