diff --git a/apps/chat/admin.py b/apps/chat/admin.py index a279a62..99911db 100644 --- a/apps/chat/admin.py +++ b/apps/chat/admin.py @@ -13,7 +13,7 @@ from django.contrib import messages from apps.chat.models import RoomMessage, ChatMessage, MessageReadStatus from utils.admin import project_admin_site, admin_url_generator from django.contrib.auth import get_user_model - +from django.db.models import Count, Q User = get_user_model() @@ -47,6 +47,30 @@ def get_allowed_users_for_room(room): return User.objects.filter(is_active=True, id__in=allowed_ids) +def get_rooms_queryset_for_user(user, queryset): + """ + Absolute Privacy Rule: + - NO ONE sees a PRIVATE chat unless they are the initiator or recipient. + - Admins/Consultants see ALL GROUP chats. + - Others see only their enrolled GROUP chats. + """ + # شرط پایه: چت‌هایی که خود شخص در آن‌ها حضور مستقیم دارد (خصوصی یا گروهی فرقی ندارد) + 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'): + # ادمین‌ها و مشاورین: چت‌های خودشان + تمام چت‌های گروهی + return queryset.filter( + personal_q | + Q(room_type=RoomMessage.RoomTypeChoices.GROUP) + ).distinct() + + # اساتید و دانشجویان: چت‌های خودشان + چت‌های گروهی که در آن عضو هستند + return 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() + # --- WIDTH ENFORCEMENT FOR SELECT2 DROPDOWNS IN INLINES --- class MinWidthInlineForm(forms.ModelForm): def __init__(self, *args, **kwargs): @@ -178,7 +202,7 @@ class RoomMessageAdmin(ModelAdmin): queryset = queryset.annotate( total_messages=Count('messages') ) - return queryset + return get_rooms_queryset_for_user(request.user, queryset) @action( description=_("Manage All Messages"), @@ -265,6 +289,26 @@ class ChatMessageAdmin(ModelAdmin): ) actions_list = ["back_to_chat_rooms"] + def get_queryset(self, request): + queryset = super().get_queryset(request) + user = request.user + + personal_q = Q(room__initiator=user) | Q(room__recipient=user) + + if user.is_superuser or user.has_role('admin') or user.has_role('super_admin') or user.has_role('consultant'): + # ادمین‌ها و مشاورین: پیام‌های خودشان + تمام پیام‌های چت‌های گروهی + return queryset.filter( + personal_q | + Q(room__room_type=RoomMessage.RoomTypeChoices.GROUP) + ).distinct() + + # اساتید و دانشجویان + return queryset.filter( + personal_q | + Q(room__room_type=RoomMessage.RoomTypeChoices.GROUP, room__course__professor=user) | + Q(room__room_type=RoomMessage.RoomTypeChoices.GROUP, room__course__participants__student=user, room__course__participants__is_active=True) + ).distinct() + # 🔔 FILTER THE SENDER DROPDOWN IN THE CHAT MESSAGE FORM def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == "sender": diff --git a/apps/chat/admin_views.py b/apps/chat/admin_views.py index 120ce13..28ffa2b 100644 --- a/apps/chat/admin_views.py +++ b/apps/chat/admin_views.py @@ -37,16 +37,23 @@ def live_chat_dashboard_view(request): return render(request, "admin/chat/live_chat.html", context) def api_get_rooms(request): - """گرفتن لیست روم‌ها بر اساس دسترسی ادمین""" + """گرفتن لیست روم‌ها بر اساس دسترسی و حریم خصوصی مطلق""" user = request.user + queryset = RoomMessage.objects.all().order_by('-updated_at') - # ادمین کل همه روم‌ها رو میبینه، استاد فقط روم‌های خودش رو - if user.is_superuser or user.has_role('admin'): - rooms = RoomMessage.objects.all().order_by('-updated_at')[:50] + 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 = RoomMessage.objects.filter( - Q(initiator=user) | Q(recipient=user) | Q(course__professor=user) - ).distinct().order_by('-updated_at')[:50] + 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: @@ -145,5 +152,89 @@ def api_send_message(request, room_id): 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) \ No newline at end of file