"""Routes available only to staff, including isolated message attachment delivery.""" from django.contrib import messages from django.db import transaction from django.utils import timezone from django.core.exceptions import PermissionDenied, ValidationError from django.http import JsonResponse from django.shortcuts import get_object_or_404, redirect from django.views.decorators.cache import never_cache from django.views.decorators.http import require_GET, require_POST from apps.attachments.delivery import attachment_response from apps.attachments.models import Attachment from .models import MessageShare, Ticket, TicketReadState from .policies import TicketPolicy from apps.messaging.read_receipts import message_read_ids from .staff_conversation import received_messages, unread_shares, visible_messages from .sharing import mark_share_opened, revoke_staff_share from .views import staff_required, mark_messages_read def received_message_share(request, share_id): from .dispatch_responses import can_read_response grant = get_object_or_404(MessageShare.objects.select_related("message__author__staff_profile", "granted_by__staff_profile"), public_id=share_id) profile = getattr(request.user, "staff_profile", None) if not can_read_response(request.user, grant.message): raise PermissionDenied if (grant.user_id != request.user.pk or grant.revoked_at or grant.message.deleted_at or not request.user.is_active or not profile or not profile.is_active): raise PermissionDenied return grant def submit_share_form(request, form, save, success_message): if not form.is_valid(): messages.error(request, "گیرنده، سطح دسترسی یا توضیح اشتراک معتبر نیست.") return try: save() except ValidationError as error: messages.error(request, " ".join(error.messages)) else: messages.success(request, success_message) @staff_required @require_GET @never_cache def inbox(request): from django.urls import reverse return redirect(reverse("staff_tickets:list") + "?tab=shared") @staff_required @require_POST def revoke_message(request, public_id, share_id): grant = get_object_or_404(MessageShare.objects.select_related("message__ticket"), public_id=share_id, message__ticket__public_id=public_id) revoke_staff_share(grant, request.user) messages.success(request, "دسترسی به پیام اشتراکی لغو شد؛ سابقه آن حفظ شده است.") return redirect("staff_tickets:detail", public_id=public_id) @staff_required @require_GET @never_cache def shared_message(request, share_id): grant = received_message_share(request, share_id) return redirect("staff_tickets:detail", public_id=grant.message.ticket.public_id) @staff_required @require_POST @never_cache @transaction.atomic def read_shared_messages(request, public_id): from .dispatch_responses import can_record_ticket_read ticket = get_object_or_404(Ticket.objects.select_for_update(), public_id=public_id) allowed = visible_messages(ticket, request.user) try: ids = message_read_ids(request) except ValidationError: return JsonResponse({"ok": False, "error": "invalid_message_ids"}, status=400) acknowledged = list(allowed.filter(public_id__in=ids, deleted_at__isnull=True)) mark_messages_read(acknowledged, request.user) accepted = [item.public_id for item in acknowledged] grants = received_messages(request.user, ticket).filter(message__public_id__in=accepted) for grant in grants: if grant.first_viewed_at is None: mark_share_opened(grant, request.user) latest = allowed.filter(deleted_at__isnull=True).order_by('-created_at', '-pk').first() if latest and latest.public_id in accepted and can_record_ticket_read(ticket, request.user): TicketReadState.objects.update_or_create(ticket=ticket, user=request.user, defaults={'last_read_at': timezone.now(), 'last_read_message': latest}) return JsonResponse({"unread_shares": unread_shares(request.user), "read_ids": [str(value) for value in accepted]}) @staff_required @require_GET @never_cache def unread_count(request): return JsonResponse({"unread_shares": unread_shares(request.user)}) @staff_required @require_GET @never_cache def shared_attachment(request, share_id, attachment_id, download=False): grant = received_message_share(request, share_id) attachment = get_object_or_404(Attachment, public_id=attachment_id, message_id=grant.message_id) return attachment_response(attachment, download=download)