""" Reports API endpoints. Handles user-submitted reports for content moderation. """ from typing import List from uuid import UUID from datetime import datetime from ninja import Router, Query from django.shortcuts import get_object_or_404 from django.db.models import Count, Avg, Q from django.db.models.functions import Extract from django.utils import timezone from apps.reports.models import Report from apps.users.permissions import require_role from api.v1.schemas import ( ReportOut, ReportCreate, ReportUpdate, ReportListOut, ReportStatsOut, MessageSchema, ErrorResponse, ) router = Router(tags=["Reports"]) def serialize_report(report: Report) -> dict: """Serialize a report to dict for output.""" return { 'id': report.id, 'entity_type': report.entity_type, 'entity_id': report.entity_id, 'report_type': report.report_type, 'description': report.description, 'status': report.status, 'reported_by_id': report.reported_by_id, 'reported_by_email': report.reported_by.email if report.reported_by else None, 'reviewed_by_id': report.reviewed_by_id, 'reviewed_by_email': report.reviewed_by.email if report.reviewed_by else None, 'reviewed_at': report.reviewed_at, 'resolution_notes': report.resolution_notes, 'created_at': report.created_at, 'updated_at': report.updated_at, } @router.post("/", response={201: ReportOut, 400: ErrorResponse, 401: ErrorResponse}) def create_report(request, data: ReportCreate): """ Submit a report (authenticated users only). Allows authenticated users to report inappropriate or inaccurate content. """ # Require authentication if not request.user or not request.user.is_authenticated: return 401, {'detail': 'Authentication required'} # Create report report = Report.objects.create( entity_type=data.entity_type, entity_id=data.entity_id, report_type=data.report_type, description=data.description, reported_by=request.user, status='pending' ) return 201, serialize_report(report) @router.get("/", response={200: ReportListOut, 401: ErrorResponse}) def list_reports( request, status: str = Query(None, description="Filter by status"), report_type: str = Query(None, description="Filter by report type"), entity_type: str = Query(None, description="Filter by entity type"), entity_id: UUID = Query(None, description="Filter by entity ID"), page: int = Query(1, ge=1), page_size: int = Query(50, ge=1, le=100), ): """ List reports. Moderators see all reports. Regular users only see their own reports. """ # Require authentication if not request.user or not request.user.is_authenticated: return 401, {'detail': 'Authentication required'} # Build queryset queryset = Report.objects.all().select_related('reported_by', 'reviewed_by') # Filter by user unless moderator is_moderator = hasattr(request.user, 'role') and request.user.role in ['moderator', 'admin'] if not is_moderator: queryset = queryset.filter(reported_by=request.user) # Apply filters if status: queryset = queryset.filter(status=status) if report_type: queryset = queryset.filter(report_type=report_type) if entity_type: queryset = queryset.filter(entity_type=entity_type) if entity_id: queryset = queryset.filter(entity_id=entity_id) # Order by date (newest first) queryset = queryset.order_by('-created_at') # Pagination total = queryset.count() total_pages = (total + page_size - 1) // page_size start = (page - 1) * page_size end = start + page_size reports = queryset[start:end] return 200, { 'items': [serialize_report(report) for report in reports], 'total': total, 'page': page, 'page_size': page_size, 'total_pages': total_pages, } @router.get("/{report_id}/", response={200: ReportOut, 404: ErrorResponse, 403: ErrorResponse}) def get_report(request, report_id: UUID): """ Get a single report by ID. Users can only view their own reports unless they are moderators. """ report = get_object_or_404( Report.objects.select_related('reported_by', 'reviewed_by'), id=report_id ) # Permission check: must be reporter or moderator is_moderator = hasattr(request.user, 'role') and request.user.role in ['moderator', 'admin'] if not is_moderator and report.reported_by != request.user: return 403, {'detail': 'You do not have permission to view this report'} return 200, serialize_report(report) @router.patch("/{report_id}/", response={200: ReportOut, 404: ErrorResponse, 403: ErrorResponse}) @require_role(['moderator', 'admin']) def update_report(request, report_id: UUID, data: ReportUpdate): """ Update a report (moderators only). Allows moderators to update report status and add resolution notes. """ report = get_object_or_404(Report, id=report_id) # Update fields if provided update_fields = [] if data.status is not None: report.status = data.status update_fields.append('status') # If status is being changed to resolved/dismissed, set reviewed fields if data.status in ['resolved', 'dismissed'] and not report.reviewed_by: report.reviewed_by = request.user report.reviewed_at = timezone.now() update_fields.extend(['reviewed_by', 'reviewed_at']) if data.resolution_notes is not None: report.resolution_notes = data.resolution_notes update_fields.append('resolution_notes') if update_fields: update_fields.append('updated_at') report.save(update_fields=update_fields) return 200, serialize_report(report) @router.get("/stats/", response={200: ReportStatsOut, 403: ErrorResponse}) @require_role(['moderator', 'admin']) def get_report_stats(request): """ Get report statistics (moderators only). Returns various statistics about reports for moderation purposes. """ queryset = Report.objects.all() # Count by status total_reports = queryset.count() pending_reports = queryset.filter(status='pending').count() reviewing_reports = queryset.filter(status='reviewing').count() resolved_reports = queryset.filter(status='resolved').count() dismissed_reports = queryset.filter(status='dismissed').count() # Count by report type reports_by_type = dict( queryset.values('report_type') .annotate(count=Count('id')) .values_list('report_type', 'count') ) # Count by entity type reports_by_entity_type = dict( queryset.values('entity_type') .annotate(count=Count('id')) .values_list('entity_type', 'count') ) # Calculate average resolution time for resolved/dismissed reports resolved_queryset = queryset.filter( status__in=['resolved', 'dismissed'], reviewed_at__isnull=False ) avg_resolution_time = None if resolved_queryset.exists(): # Calculate time difference in hours from django.db.models import F, ExpressionWrapper, DurationField from datetime import timedelta time_diffs = [] for report in resolved_queryset: if report.reviewed_at and report.created_at: diff = (report.reviewed_at - report.created_at).total_seconds() / 3600 time_diffs.append(diff) if time_diffs: avg_resolution_time = sum(time_diffs) / len(time_diffs) return 200, { 'total_reports': total_reports, 'pending_reports': pending_reports, 'reviewing_reports': reviewing_reports, 'resolved_reports': resolved_reports, 'dismissed_reports': dismissed_reports, 'reports_by_type': reports_by_type, 'reports_by_entity_type': reports_by_entity_type, 'average_resolution_time_hours': avg_resolution_time, } @router.delete("/{report_id}/", response={200: MessageSchema, 404: ErrorResponse, 403: ErrorResponse}) @require_role(['moderator', 'admin']) def delete_report(request, report_id: UUID): """ Delete a report (moderators only). Permanently removes a report from the system. """ report = get_object_or_404(Report, id=report_id) report.delete() return 200, { 'message': 'Report deleted successfully', 'success': True }