Refactor code structure and remove redundant changes

This commit is contained in:
pacnpal
2025-11-09 16:31:34 -05:00
parent 2884bc23ce
commit eb68cf40c6
1080 changed files with 27361 additions and 56687 deletions

View File

@@ -0,0 +1,263 @@
"""
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
}