mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-21 23:11:12 -05:00
Add ReviewEvent model and ReviewSubmissionService for review management
- Created a new ReviewEvent model to track review events with fields for content, rating, moderation status, and timestamps. - Added ForeignKey relationships to connect ReviewEvent with ContentSubmission, User, and Review. - Implemented ReviewSubmissionService to handle review submissions, including creation, updates, and moderation workflows. - Introduced atomic transactions to ensure data integrity during review submissions and updates. - Added logging for review submission and moderation actions for better traceability. - Implemented validation to prevent duplicate reviews and ensure only the review owner can update their review.
This commit is contained in:
@@ -12,6 +12,7 @@ from django.core.exceptions import ValidationError, PermissionDenied
|
||||
|
||||
from apps.moderation.models import ContentSubmission, SubmissionItem
|
||||
from apps.moderation.services import ModerationService
|
||||
from apps.users.permissions import jwt_auth, require_auth
|
||||
from api.v1.schemas import (
|
||||
ContentSubmissionCreate,
|
||||
ContentSubmissionOut,
|
||||
@@ -109,20 +110,20 @@ def _get_entity(entity_type: str, entity_id: UUID):
|
||||
# Submission Endpoints
|
||||
# ============================================================================
|
||||
|
||||
@router.post('/submissions', response={201: ContentSubmissionOut, 400: ErrorResponse, 401: ErrorResponse})
|
||||
@router.post('/submissions', response={201: ContentSubmissionOut, 400: ErrorResponse, 401: ErrorResponse}, auth=jwt_auth)
|
||||
@require_auth
|
||||
def create_submission(request, data: ContentSubmissionCreate):
|
||||
"""
|
||||
Create a new content submission.
|
||||
|
||||
Creates a submission with multiple items representing field changes.
|
||||
If auto_submit is True, the submission is immediately moved to pending state.
|
||||
"""
|
||||
# TODO: Require authentication
|
||||
# For now, use a test user or get from request
|
||||
from apps.users.models import User
|
||||
user = User.objects.first() # TEMP: Get first user for testing
|
||||
|
||||
if not user:
|
||||
**Authentication:** Required
|
||||
"""
|
||||
user = request.auth
|
||||
|
||||
if not user or not user.is_authenticated:
|
||||
return 401, {'detail': 'Authentication required'}
|
||||
|
||||
try:
|
||||
@@ -227,14 +228,18 @@ def get_submission(request, submission_id: UUID):
|
||||
return 404, {'detail': 'Submission not found'}
|
||||
|
||||
|
||||
@router.delete('/submissions/{submission_id}', response={204: None, 403: ErrorResponse, 404: ErrorResponse})
|
||||
@router.delete('/submissions/{submission_id}', response={204: None, 403: ErrorResponse, 404: ErrorResponse}, auth=jwt_auth)
|
||||
@require_auth
|
||||
def delete_submission(request, submission_id: UUID):
|
||||
"""
|
||||
Delete a submission (only if draft/pending and owned by user).
|
||||
|
||||
**Authentication:** Required
|
||||
"""
|
||||
# TODO: Get current user from request
|
||||
from apps.users.models import User
|
||||
user = User.objects.first() # TEMP
|
||||
user = request.auth
|
||||
|
||||
if not user or not user.is_authenticated:
|
||||
return 401, {'detail': 'Authentication required'}
|
||||
|
||||
try:
|
||||
ModerationService.delete_submission(submission_id, user)
|
||||
@@ -254,17 +259,26 @@ def delete_submission(request, submission_id: UUID):
|
||||
|
||||
@router.post(
|
||||
'/submissions/{submission_id}/start-review',
|
||||
response={200: ContentSubmissionOut, 400: ErrorResponse, 403: ErrorResponse, 404: ErrorResponse}
|
||||
response={200: ContentSubmissionOut, 400: ErrorResponse, 403: ErrorResponse, 404: ErrorResponse},
|
||||
auth=jwt_auth
|
||||
)
|
||||
@require_auth
|
||||
def start_review(request, submission_id: UUID, data: StartReviewRequest):
|
||||
"""
|
||||
Start reviewing a submission (lock it for 15 minutes).
|
||||
|
||||
Only moderators can start reviews.
|
||||
|
||||
**Authentication:** Required (Moderator role)
|
||||
"""
|
||||
# TODO: Get current user (moderator) from request
|
||||
from apps.users.models import User
|
||||
user = User.objects.first() # TEMP
|
||||
user = request.auth
|
||||
|
||||
if not user or not user.is_authenticated:
|
||||
return 401, {'detail': 'Authentication required'}
|
||||
|
||||
# Check moderator permission
|
||||
if not hasattr(user, 'role') or not user.role.is_moderator:
|
||||
return 403, {'detail': 'Moderator permission required'}
|
||||
|
||||
try:
|
||||
submission = ModerationService.start_review(submission_id, user)
|
||||
@@ -280,18 +294,27 @@ def start_review(request, submission_id: UUID, data: StartReviewRequest):
|
||||
|
||||
@router.post(
|
||||
'/submissions/{submission_id}/approve',
|
||||
response={200: ApprovalResponse, 400: ErrorResponse, 403: ErrorResponse, 404: ErrorResponse}
|
||||
response={200: ApprovalResponse, 400: ErrorResponse, 403: ErrorResponse, 404: ErrorResponse},
|
||||
auth=jwt_auth
|
||||
)
|
||||
@require_auth
|
||||
def approve_submission(request, submission_id: UUID, data: ApproveRequest):
|
||||
"""
|
||||
Approve an entire submission and apply all changes.
|
||||
|
||||
Uses atomic transactions - all changes are applied or none are.
|
||||
Only moderators can approve submissions.
|
||||
|
||||
**Authentication:** Required (Moderator role)
|
||||
"""
|
||||
# TODO: Get current user (moderator) from request
|
||||
from apps.users.models import User
|
||||
user = User.objects.first() # TEMP
|
||||
user = request.auth
|
||||
|
||||
if not user or not user.is_authenticated:
|
||||
return 401, {'detail': 'Authentication required'}
|
||||
|
||||
# Check moderator permission
|
||||
if not hasattr(user, 'role') or not user.role.is_moderator:
|
||||
return 403, {'detail': 'Moderator permission required'}
|
||||
|
||||
try:
|
||||
submission = ModerationService.approve_submission(submission_id, user)
|
||||
@@ -312,18 +335,27 @@ def approve_submission(request, submission_id: UUID, data: ApproveRequest):
|
||||
|
||||
@router.post(
|
||||
'/submissions/{submission_id}/approve-selective',
|
||||
response={200: SelectiveApprovalResponse, 400: ErrorResponse, 403: ErrorResponse, 404: ErrorResponse}
|
||||
response={200: SelectiveApprovalResponse, 400: ErrorResponse, 403: ErrorResponse, 404: ErrorResponse},
|
||||
auth=jwt_auth
|
||||
)
|
||||
@require_auth
|
||||
def approve_selective(request, submission_id: UUID, data: ApproveSelectiveRequest):
|
||||
"""
|
||||
Approve only specific items in a submission.
|
||||
|
||||
Allows moderators to approve some changes while leaving others pending or rejected.
|
||||
Uses atomic transactions for data integrity.
|
||||
|
||||
**Authentication:** Required (Moderator role)
|
||||
"""
|
||||
# TODO: Get current user (moderator) from request
|
||||
from apps.users.models import User
|
||||
user = User.objects.first() # TEMP
|
||||
user = request.auth
|
||||
|
||||
if not user or not user.is_authenticated:
|
||||
return 401, {'detail': 'Authentication required'}
|
||||
|
||||
# Check moderator permission
|
||||
if not hasattr(user, 'role') or not user.role.is_moderator:
|
||||
return 403, {'detail': 'Moderator permission required'}
|
||||
|
||||
try:
|
||||
result = ModerationService.approve_selective(
|
||||
@@ -348,18 +380,27 @@ def approve_selective(request, submission_id: UUID, data: ApproveSelectiveReques
|
||||
|
||||
@router.post(
|
||||
'/submissions/{submission_id}/reject',
|
||||
response={200: ApprovalResponse, 400: ErrorResponse, 403: ErrorResponse, 404: ErrorResponse}
|
||||
response={200: ApprovalResponse, 400: ErrorResponse, 403: ErrorResponse, 404: ErrorResponse},
|
||||
auth=jwt_auth
|
||||
)
|
||||
@require_auth
|
||||
def reject_submission(request, submission_id: UUID, data: RejectRequest):
|
||||
"""
|
||||
Reject an entire submission.
|
||||
|
||||
All pending items are rejected with the provided reason.
|
||||
Only moderators can reject submissions.
|
||||
|
||||
**Authentication:** Required (Moderator role)
|
||||
"""
|
||||
# TODO: Get current user (moderator) from request
|
||||
from apps.users.models import User
|
||||
user = User.objects.first() # TEMP
|
||||
user = request.auth
|
||||
|
||||
if not user or not user.is_authenticated:
|
||||
return 401, {'detail': 'Authentication required'}
|
||||
|
||||
# Check moderator permission
|
||||
if not hasattr(user, 'role') or not user.role.is_moderator:
|
||||
return 403, {'detail': 'Moderator permission required'}
|
||||
|
||||
try:
|
||||
submission = ModerationService.reject_submission(submission_id, user, data.reason)
|
||||
@@ -380,17 +421,26 @@ def reject_submission(request, submission_id: UUID, data: RejectRequest):
|
||||
|
||||
@router.post(
|
||||
'/submissions/{submission_id}/reject-selective',
|
||||
response={200: SelectiveRejectionResponse, 400: ErrorResponse, 403: ErrorResponse, 404: ErrorResponse}
|
||||
response={200: SelectiveRejectionResponse, 400: ErrorResponse, 403: ErrorResponse, 404: ErrorResponse},
|
||||
auth=jwt_auth
|
||||
)
|
||||
@require_auth
|
||||
def reject_selective(request, submission_id: UUID, data: RejectSelectiveRequest):
|
||||
"""
|
||||
Reject only specific items in a submission.
|
||||
|
||||
Allows moderators to reject some changes while leaving others pending or approved.
|
||||
|
||||
**Authentication:** Required (Moderator role)
|
||||
"""
|
||||
# TODO: Get current user (moderator) from request
|
||||
from apps.users.models import User
|
||||
user = User.objects.first() # TEMP
|
||||
user = request.auth
|
||||
|
||||
if not user or not user.is_authenticated:
|
||||
return 401, {'detail': 'Authentication required'}
|
||||
|
||||
# Check moderator permission
|
||||
if not hasattr(user, 'role') or not user.role.is_moderator:
|
||||
return 403, {'detail': 'Moderator permission required'}
|
||||
|
||||
try:
|
||||
result = ModerationService.reject_selective(
|
||||
@@ -456,16 +506,20 @@ def get_reviewing_queue(request, page: int = 1, page_size: int = 50):
|
||||
return list_submissions(request, status='reviewing', page=page, page_size=page_size)
|
||||
|
||||
|
||||
@router.get('/queue/my-submissions', response=SubmissionListOut)
|
||||
@router.get('/queue/my-submissions', response=SubmissionListOut, auth=jwt_auth)
|
||||
@require_auth
|
||||
def get_my_submissions(request, page: int = 1, page_size: int = 50):
|
||||
"""
|
||||
Get current user's submissions.
|
||||
|
||||
Returns all submissions created by the authenticated user.
|
||||
|
||||
**Authentication:** Required
|
||||
"""
|
||||
# TODO: Get current user from request
|
||||
from apps.users.models import User
|
||||
user = User.objects.first() # TEMP
|
||||
user = request.auth
|
||||
|
||||
if not user or not user.is_authenticated:
|
||||
return {'items': [], 'total': 0, 'page': page, 'page_size': page_size, 'total_pages': 0}
|
||||
|
||||
# Validate page_size
|
||||
page_size = min(page_size, 100)
|
||||
|
||||
Reference in New Issue
Block a user