mirror of
https://github.com/pacnpal/thrillwiki_django_no_react.git
synced 2026-01-01 22:07:03 -05:00
feat: Implement initial schema and add various API, service, and management command enhancements across the application.
This commit is contained in:
167
backend/apps/api/v1/responses.py
Normal file
167
backend/apps/api/v1/responses.py
Normal file
@@ -0,0 +1,167 @@
|
||||
"""
|
||||
Standardized API response helpers for ThrillWiki.
|
||||
|
||||
This module provides consistent response formatting across all API endpoints:
|
||||
|
||||
Success responses:
|
||||
- Action completed: {"detail": "Success message"}
|
||||
- With data: {"detail": "...", "data": {...}}
|
||||
|
||||
Error responses:
|
||||
- Validation: {"field": ["error"]} (DRF default)
|
||||
- Application: {"detail": "Error message", "code": "ERROR_CODE"}
|
||||
|
||||
Usage:
|
||||
from apps.api.v1.responses import success_response, error_response
|
||||
|
||||
# Success
|
||||
return success_response("Avatar saved successfully")
|
||||
|
||||
# Error
|
||||
return error_response("User not found", code="NOT_FOUND", status_code=404)
|
||||
"""
|
||||
|
||||
from rest_framework import status
|
||||
from rest_framework.response import Response
|
||||
|
||||
|
||||
# Standard error codes for machine-readable error handling
|
||||
class ErrorCodes:
|
||||
"""Standard error codes for API responses."""
|
||||
|
||||
# Authentication / Authorization
|
||||
UNAUTHORIZED = "UNAUTHORIZED"
|
||||
FORBIDDEN = "FORBIDDEN"
|
||||
INVALID_CREDENTIALS = "INVALID_CREDENTIALS"
|
||||
TOKEN_EXPIRED = "TOKEN_EXPIRED"
|
||||
TOKEN_INVALID = "TOKEN_INVALID"
|
||||
|
||||
# Resource errors
|
||||
NOT_FOUND = "NOT_FOUND"
|
||||
ALREADY_EXISTS = "ALREADY_EXISTS"
|
||||
CONFLICT = "CONFLICT"
|
||||
|
||||
# Validation errors
|
||||
VALIDATION_ERROR = "VALIDATION_ERROR"
|
||||
INVALID_INPUT = "INVALID_INPUT"
|
||||
MISSING_FIELD = "MISSING_FIELD"
|
||||
|
||||
# Operation errors
|
||||
OPERATION_FAILED = "OPERATION_FAILED"
|
||||
PERMISSION_DENIED = "PERMISSION_DENIED"
|
||||
RATE_LIMITED = "RATE_LIMITED"
|
||||
|
||||
# User-specific errors
|
||||
USER_NOT_FOUND = "USER_NOT_FOUND"
|
||||
USER_INACTIVE = "USER_INACTIVE"
|
||||
USER_BANNED = "USER_BANNED"
|
||||
CANNOT_DELETE_SUPERUSER = "CANNOT_DELETE_SUPERUSER"
|
||||
CANNOT_DELETE_SELF = "CANNOT_DELETE_SELF"
|
||||
|
||||
# Verification errors
|
||||
VERIFICATION_EXPIRED = "VERIFICATION_EXPIRED"
|
||||
VERIFICATION_INVALID = "VERIFICATION_INVALID"
|
||||
ALREADY_VERIFIED = "ALREADY_VERIFIED"
|
||||
|
||||
# External service errors
|
||||
EXTERNAL_SERVICE_ERROR = "EXTERNAL_SERVICE_ERROR"
|
||||
CLOUDFLARE_ERROR = "CLOUDFLARE_ERROR"
|
||||
|
||||
|
||||
def success_response(
|
||||
detail: str,
|
||||
data: dict | None = None,
|
||||
status_code: int = status.HTTP_200_OK,
|
||||
) -> Response:
|
||||
"""
|
||||
Create a standardized success response.
|
||||
|
||||
Args:
|
||||
detail: Human-readable success message
|
||||
data: Optional additional data to include
|
||||
status_code: HTTP status code (default 200)
|
||||
|
||||
Returns:
|
||||
DRF Response object
|
||||
|
||||
Example:
|
||||
return success_response("Avatar saved successfully")
|
||||
return success_response("User created", data={"id": user.id}, status_code=201)
|
||||
"""
|
||||
response_data = {"detail": detail}
|
||||
if data:
|
||||
response_data.update(data)
|
||||
return Response(response_data, status=status_code)
|
||||
|
||||
|
||||
def error_response(
|
||||
detail: str,
|
||||
code: str | None = None,
|
||||
status_code: int = status.HTTP_400_BAD_REQUEST,
|
||||
extra: dict | None = None,
|
||||
) -> Response:
|
||||
"""
|
||||
Create a standardized error response.
|
||||
|
||||
Args:
|
||||
detail: Human-readable error message
|
||||
code: Machine-readable error code from ErrorCodes
|
||||
status_code: HTTP status code (default 400)
|
||||
extra: Optional additional data to include
|
||||
|
||||
Returns:
|
||||
DRF Response object
|
||||
|
||||
Example:
|
||||
return error_response("User not found", code=ErrorCodes.NOT_FOUND, status_code=404)
|
||||
return error_response("Invalid input", code=ErrorCodes.VALIDATION_ERROR)
|
||||
"""
|
||||
response_data = {"detail": detail}
|
||||
if code:
|
||||
response_data["code"] = code
|
||||
if extra:
|
||||
response_data.update(extra)
|
||||
return Response(response_data, status=status_code)
|
||||
|
||||
|
||||
def created_response(detail: str, data: dict | None = None) -> Response:
|
||||
"""Convenience wrapper for 201 Created responses."""
|
||||
return success_response(detail, data=data, status_code=status.HTTP_201_CREATED)
|
||||
|
||||
|
||||
def not_found_response(detail: str = "Resource not found") -> Response:
|
||||
"""Convenience wrapper for 404 Not Found responses."""
|
||||
return error_response(
|
||||
detail,
|
||||
code=ErrorCodes.NOT_FOUND,
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
)
|
||||
|
||||
|
||||
def forbidden_response(detail: str = "Permission denied") -> Response:
|
||||
"""Convenience wrapper for 403 Forbidden responses."""
|
||||
return error_response(
|
||||
detail,
|
||||
code=ErrorCodes.FORBIDDEN,
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
)
|
||||
|
||||
|
||||
def unauthorized_response(detail: str = "Authentication required") -> Response:
|
||||
"""Convenience wrapper for 401 Unauthorized responses."""
|
||||
return error_response(
|
||||
detail,
|
||||
code=ErrorCodes.UNAUTHORIZED,
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"ErrorCodes",
|
||||
"success_response",
|
||||
"error_response",
|
||||
"created_response",
|
||||
"not_found_response",
|
||||
"forbidden_response",
|
||||
"unauthorized_response",
|
||||
]
|
||||
Reference in New Issue
Block a user