import json import requests import threading # 🟢 برای اجرای بک‌گراند from django.conf import settings from django.http import JsonResponse from django.shortcuts import render, get_object_or_404 from django.views.decorators.http import require_http_methods from django.db.models import Q, Count from django.utils.translation import gettext_lazy as _ from apps.chat.models import RoomMessage, ChatMessage from utils.admin import project_admin_site def send_webhook_to_fastapi(room_id, message_data): fastapi_url = f"{getattr(settings, 'FASTAPI_SERVER_URL', 'http://127.0.0.1:8000')}/api/internal/webhook/chat-message/" headers = { "Authorization": getattr(settings, 'INTERNAL_WEBHOOK_SECRET', 'super-secret-key-12345'), "Content-Type": "application/json" } payload = { "room_id": room_id, "message": message_data } try: # با تایم‌اوت 2 ثانیه می‌فرستیم که اگر سرور گیر بود، پروسه متوقف نشود requests.post(fastapi_url, json=payload, headers=headers, timeout=2.0) except Exception as e: print(f"⚠️ Webhook to FastAPI failed: {e}") def live_chat_dashboard_view(request): """رندر کردن اسکلت اصلی صفحه چت""" context = { **project_admin_site.each_context(request), "title": _("Live Support Chat"), } return render(request, "admin/chat/live_chat.html", context) def api_get_rooms(request): """گرفتن لیست روم‌ها بر اساس دسترسی و حریم خصوصی مطلق""" user = request.user queryset = RoomMessage.objects.annotate( admin_unread_count=Count( 'messages', filter=Q(messages__is_read=False) & ~Q(messages__sender=user) ) ).order_by('-updated_at') personal_q = Q(initiator=user) | Q(recipient=user) if user.is_superuser or user.has_role('admin') or user.has_role('super_admin') or user.has_role('consultant'): rooms = queryset.filter( personal_q | Q(room_type=RoomMessage.RoomTypeChoices.GROUP) ).distinct()[:50] else: rooms = queryset.filter( personal_q | Q(room_type=RoomMessage.RoomTypeChoices.GROUP, course__professor=user) | Q(room_type=RoomMessage.RoomTypeChoices.GROUP, course__participants__student=user, course__participants__is_active=True) ).distinct()[:50] data = [] for r in rooms: title = r.name or f"Room #{r.id}" if r.room_type == 'group' and r.course: title = r.course.title unread_count = getattr(r, 'admin_unread_count', 0) data.append({ "id": r.id, "title": title, "type": r.room_type, "unread": unread_count, "time": r.updated_at.strftime("%H:%M") if r.updated_at else "" }) return JsonResponse({"rooms": data}) def api_get_messages(request, room_id): """گرفتن پیام‌های یک روم خاص (فقط پیام‌های جدیدتر از last_id)""" last_id = int(request.GET.get('last_id', 0)) room = get_object_or_404(RoomMessage, id=room_id) messages = ChatMessage.objects.filter( room=room, id__gt=last_id, # جادوی پولینگ: فقط آیدی‌های بزرگتر از آخرین پیامی که داریم رو بگیر is_deleted=False ).order_by('id') # 🟢 مارک کردن پیام‌ها به عنوان خوانده شده (سین کردن) برای تمام انواع چت if messages.exists(): # پیام‌های طرف مقابل را سین کن unread_msgs = messages.filter(is_read=False).exclude(sender=request.user) if unread_msgs.exists(): unread_msgs.update(is_read=True) if room.room_type == RoomMessage.RoomTypeChoices.GROUP: # همگام‌سازی با فیلد استاتیک برای جلوگیری از مشکلات احتمالی در FastAPI if room.unread_messages_count > 0: room.unread_messages_count = 0 room.save(update_fields=['unread_messages_count']) data = [] for m in messages: avatar = m.sender.avatar.url if getattr(m.sender, 'avatar', None) else None sender_name = m.sender.fullname or m.sender.email or "Unknown" data.append({ "id": m.id, "sender_id": m.sender_id, "sender_name": sender_name, "avatar": avatar, "content": m.content, "type": m.content_type, "file_url": m.file_url, # از پراپرتی مدلت استفاده کردیم "sent_at": m.sent_at.strftime("%H:%M"), "is_me": m.sender_id == request.user.id }) return JsonResponse({"messages": data}) @require_http_methods(["POST"]) def api_send_message(request, room_id): """ارسال پیام جدید از طریق ایجکس (AJAX)""" try: body = json.loads(request.body) content = body.get('content', '').strip() if not content: return JsonResponse({"error": "Empty content"}, status=400) room = get_object_or_404(RoomMessage, id=room_id) # ذخیره در دیتابیس msg = ChatMessage.objects.create( room=room, sender=request.user, content=content, content_type='text' ) # آپدیت کردن زمان روم room.updated_at = msg.sent_at room.save(update_fields=['updated_at']) # 🟢 ساخت ساختار JSON پیام دقیقاً شبیه به چیزی که FastAPI به فرانت می‌فرستد avatar_url = request.user.avatar.url if getattr(request.user, 'avatar', None) else None sender_name = request.user.fullname or request.user.email or "Support" message_data = { "id": msg.id, "room_id": room_id, "sender": { "id": request.user.id, "email": request.user.email, "fullname": sender_name, "user_type": getattr(request.user, 'user_type', 'admin'), "avatar": avatar_url }, "content": msg.content, "content_type": msg.content_type, "content_size": msg.content_size, "sent_at": msg.sent_at.isoformat(), "metadata": msg.message_metadata, "updated_at": None, "is_deleted": False } # 🟢 شلیک به سمت FastAPI در یک Thread جداگانه threading.Thread(target=send_webhook_to_fastapi, args=(room_id, message_data)).start() return JsonResponse({"success": True, "message_id": msg.id}) except Exception as e: return JsonResponse({"error": str(e)}, status=500) def api_get_users(request): """ برگرداندن لیست کاربران فعال که ایمیل دارند (برای شروع چت جدید) """ from django.contrib.auth import get_user_model User = get_user_model() search_term = request.GET.get('q', '').strip() # کاربرانی که فعال هستند، خود شخصِ فعلی نیستند و حتماً ایمیل دارند users_qs = User.objects.filter( is_active=True, email__isnull=False ).exclude(id=request.user.id) if search_term: users_qs = users_qs.filter( Q(email__icontains=search_term) | Q(fullname__icontains=search_term) ) users_qs = users_qs.order_by('-date_joined')[:50] # محدود کردن به 50 نفر اول data = [] for u in users_qs: name = u.fullname or u.email data.append({ "id": u.id, "name": name, "role": getattr(u, 'user_type', 'Member') }) return JsonResponse({"users": data}) @require_http_methods(["POST"]) def api_create_or_get_room(request): """ اگر بین ادمین فعلی و کاربرِ انتخابی چت وجود داشت، آیدی‌اش را برمی‌گرداند اگر وجود نداشت، یک Private Room جدید می‌سازد """ try: body = json.loads(request.body) recipient_id = body.get('user_id') if not recipient_id: return JsonResponse({"error": "No user ID provided"}, status=400) from django.contrib.auth import get_user_model User = get_user_model() recipient = get_object_or_404(User, id=recipient_id) # بررسی می‌کنیم آیا قبلا رومی بین این دو نفر ساخته شده؟ existing_room = RoomMessage.objects.filter( room_type='private' ).filter( Q(initiator=request.user, recipient=recipient) | Q(initiator=recipient, recipient=request.user) ).first() if existing_room: # روم از قبل وجود دارد return JsonResponse({ "success": True, "room_id": existing_room.id, "title": existing_room.name or recipient.fullname or recipient.email }) # روم وجود ندارد، می‌سازیم new_room = RoomMessage.objects.create( name=f"Chat with {recipient.fullname or recipient.email}", room_type='private', initiator=request.user, recipient=recipient ) return JsonResponse({ "success": True, "room_id": new_room.id, "title": new_room.name }) except Exception as e: return JsonResponse({"error": str(e)}, status=500)