You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
297 lines
11 KiB
297 lines
11 KiB
import random
|
|
from rest_framework import status
|
|
from rest_framework.generics import CreateAPIView, GenericAPIView, ListAPIView
|
|
from rest_framework.views import APIView
|
|
from rest_framework.response import Response
|
|
from rest_framework import serializers
|
|
from rest_framework.permissions import AllowAny
|
|
from drf_yasg.utils import swagger_auto_schema
|
|
from drf_yasg import openapi
|
|
|
|
from rest_framework.authtoken.models import Token
|
|
from apps.account.models import User
|
|
from apps.api.models import Comment, AppVersion
|
|
from apps.api.serializers import (
|
|
AppVersionSerializer,
|
|
CommentSerializer,
|
|
SupportMessageCreateSerializer,
|
|
)
|
|
from utils.pagination import StandardResultsSetPagination
|
|
|
|
class HomeSerializer(serializers.Serializer):
|
|
token = serializers.CharField()
|
|
|
|
import json
|
|
from django.conf import settings
|
|
|
|
COUNTRIES_FILE_PATH = settings.BASE_DIR / 'utils' / 'country_city_list' / 'optimized_countries.json'
|
|
CITIES_FILE_PATH = settings.BASE_DIR / 'utils' / 'country_city_list' / 'optimized_cities.json'
|
|
|
|
def get_localized_name(translations, target_lang='en'):
|
|
if not translations:
|
|
return ""
|
|
for t in translations:
|
|
if t.get('language_code') == target_lang:
|
|
return t.get('title')
|
|
for t in translations:
|
|
if t.get('language_code') == 'en':
|
|
return t.get('title')
|
|
return translations[0].get('title', '')
|
|
|
|
def get_localized_cities(cities_data, country_code, target_lang='en'):
|
|
country_cities = cities_data.get(country_code.upper())
|
|
if not country_cities:
|
|
return []
|
|
for item in country_cities:
|
|
if item.get('language_code') == target_lang:
|
|
return item.get('title', [])
|
|
for item in country_cities:
|
|
if item.get('language_code') == 'en':
|
|
return item.get('title', [])
|
|
return country_cities[0].get('title', [])
|
|
|
|
|
|
# test class generate token
|
|
class HomeView(GenericAPIView):
|
|
serializer_class = HomeSerializer
|
|
|
|
@swagger_auto_schema(
|
|
operation_description="Health check and token test endpoint. Optionally reads BUILD_NUMBER from headers.",
|
|
manual_parameters=[
|
|
openapi.Parameter(
|
|
name='BUILD_NUMBER',
|
|
in_=openapi.IN_HEADER,
|
|
description='Client build number',
|
|
type=openapi.TYPE_STRING,
|
|
required=False
|
|
)
|
|
],
|
|
responses={
|
|
200: openapi.Response(
|
|
description="OK",
|
|
schema=HomeSerializer()
|
|
)
|
|
}
|
|
)
|
|
def get(self, request):
|
|
# Get build_number from headers
|
|
build_number = request.META.get('HTTP_BUILD_NUMBER')
|
|
|
|
# Print the build_number
|
|
print(f"Build Number: {build_number}")
|
|
|
|
return Response({'token': "ok", 'build_number': build_number})
|
|
|
|
class CountryView(APIView):
|
|
permission_classes = [AllowAny]
|
|
|
|
@swagger_auto_schema(
|
|
operation_description="List of optimized countries with codes and names. Supports locale translation based on request header (Accept-Language).",
|
|
responses={
|
|
200: openapi.Response(
|
|
description="List of countries sorted alphabetically by name",
|
|
schema=openapi.Schema(
|
|
type=openapi.TYPE_ARRAY,
|
|
items=openapi.Schema(
|
|
type=openapi.TYPE_OBJECT,
|
|
properties={
|
|
'code': openapi.Schema(type=openapi.TYPE_STRING, description="ISO 3166-1 alpha-2 country code", example="IR"),
|
|
'name': openapi.Schema(type=openapi.TYPE_STRING, description="Localized country name", example="Iran (Islamic Republic of)")
|
|
}
|
|
)
|
|
),
|
|
examples={
|
|
"application/json": [
|
|
{"code": "CN", "name": "China"},
|
|
{"code": "IR", "name": "Iran (Islamic Republic of)"},
|
|
{"code": "RU", "name": "Russian Federation (the)"},
|
|
{"code": "TR", "name": "Türkiye"}
|
|
]
|
|
}
|
|
)
|
|
}
|
|
)
|
|
def get(self, request):
|
|
try:
|
|
with open(COUNTRIES_FILE_PATH, 'r', encoding='utf-8') as f:
|
|
countries_data = json.load(f)
|
|
except Exception as e:
|
|
return Response({"error": "Failed to load countries data"}, status=500)
|
|
|
|
lang = getattr(request, 'LANGUAGE_CODE', 'en')
|
|
result = []
|
|
for code, translations in countries_data.items():
|
|
name = get_localized_name(translations, lang)
|
|
result.append({
|
|
"code": code,
|
|
"name": name
|
|
})
|
|
|
|
# Sort countries alphabetically by name
|
|
result.sort(key=lambda x: x["name"])
|
|
return Response(result, status=200)
|
|
|
|
class CityListView(APIView):
|
|
permission_classes = [AllowAny]
|
|
|
|
@swagger_auto_schema(
|
|
operation_description="List of cities filtered by country code. Supports locale translation based on request header (Accept-Language).",
|
|
manual_parameters=[
|
|
openapi.Parameter(
|
|
name='country_code',
|
|
in_=openapi.IN_QUERY,
|
|
description='Two-letter country code (e.g. IR, CN, TR)',
|
|
type=openapi.TYPE_STRING,
|
|
required=True,
|
|
example="IR"
|
|
)
|
|
],
|
|
responses={
|
|
200: openapi.Response(
|
|
description="List of city names sorted alphabetically",
|
|
schema=openapi.Schema(
|
|
type=openapi.TYPE_ARRAY,
|
|
items=openapi.Schema(type=openapi.TYPE_STRING, description="City name", example="Abadan")
|
|
),
|
|
examples={
|
|
"application/json": [
|
|
"Abadan",
|
|
"Abadeh",
|
|
"Abhar",
|
|
"Ahvaz",
|
|
"Tehran"
|
|
]
|
|
}
|
|
),
|
|
400: openapi.Response(
|
|
description="Bad Request - country_code missing or invalid",
|
|
schema=openapi.Schema(
|
|
type=openapi.TYPE_OBJECT,
|
|
properties={
|
|
'error': openapi.Schema(type=openapi.TYPE_STRING, description="Error message description", example="country_code query parameter is required")
|
|
}
|
|
),
|
|
examples={
|
|
"application/json": {
|
|
"error": "country_code query parameter is required"
|
|
}
|
|
}
|
|
)
|
|
}
|
|
)
|
|
def get(self, request):
|
|
country_code = request.query_params.get('country_code')
|
|
if not country_code:
|
|
return Response({"error": "country_code query parameter is required"}, status=400)
|
|
|
|
try:
|
|
with open(CITIES_FILE_PATH, 'r', encoding='utf-8') as f:
|
|
cities_data = json.load(f)
|
|
except Exception as e:
|
|
return Response({"error": "Failed to load cities data"}, status=500)
|
|
|
|
lang = getattr(request, 'LANGUAGE_CODE', 'en')
|
|
cities = get_localized_cities(cities_data, country_code, lang)
|
|
|
|
if not cities and country_code.upper() not in cities_data:
|
|
return Response({"error": f"No cities found or invalid country code: {country_code}"}, status=400)
|
|
|
|
# Sort cities alphabetically
|
|
sorted_cities = sorted(cities)
|
|
return Response(sorted_cities, status=200)
|
|
|
|
|
|
class CommentListAPIView(ListAPIView):
|
|
"""
|
|
API view to list comments ordered by order field and creation date
|
|
"""
|
|
queryset = Comment.objects.all()
|
|
serializer_class = CommentSerializer
|
|
permission_classes = [AllowAny]
|
|
ordering = ['order', '-created_at'] # Order by order field first, then by newest
|
|
pagination_class = StandardResultsSetPagination
|
|
|
|
@swagger_auto_schema(
|
|
operation_description="List comments ordered by 'order' then '-created_at'",
|
|
responses={
|
|
200: openapi.Response(
|
|
description="List of comments",
|
|
schema=CommentSerializer(many=True)
|
|
)
|
|
}
|
|
)
|
|
def get(self, request, *args, **kwargs):
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
def get_queryset(self):
|
|
queryset = super().get_queryset()
|
|
return queryset.order_by('order', '-created_at')
|
|
|
|
|
|
class AppVersionListAPIView(ListAPIView):
|
|
queryset = AppVersion.objects.all().order_by('-created_at')
|
|
serializer_class = AppVersionSerializer
|
|
permission_classes = [AllowAny]
|
|
pagination_class = StandardResultsSetPagination
|
|
@swagger_auto_schema(
|
|
operation_description="List all app versions with fields.",
|
|
responses={
|
|
200: openapi.Response(
|
|
description="List of app versions",
|
|
schema=AppVersionSerializer(many=True)
|
|
)
|
|
}
|
|
)
|
|
def get(self, request, *args, **kwargs):
|
|
return super().get(request, *args, **kwargs)
|
|
|
|
|
|
class SupportMessageCreateAPIView(CreateAPIView):
|
|
serializer_class = SupportMessageCreateSerializer
|
|
permission_classes = [AllowAny]
|
|
|
|
@swagger_auto_schema(
|
|
tags=["Support"],
|
|
operation_summary="Create support message",
|
|
operation_description=(
|
|
"Create a new support message from the website or mobile contact form.\n\n"
|
|
"This endpoint is public and does not require authentication.\n\n"
|
|
"Required fields:\n"
|
|
"- `sender_name`: full name of the sender\n"
|
|
"- `sender_email`: valid email address\n"
|
|
"- `subject`: message subject\n"
|
|
"- `message`: message body\n\n"
|
|
"Optional fields:\n"
|
|
"- `sender_phone`: sender phone number in international format\n\n"
|
|
"Phone number validation follows the same rules used in the account module."
|
|
),
|
|
request_body=SupportMessageCreateSerializer,
|
|
responses={
|
|
status.HTTP_201_CREATED: openapi.Response(
|
|
description="Support message created successfully.",
|
|
schema=SupportMessageCreateSerializer,
|
|
examples={
|
|
"application/json": {
|
|
"id": 12,
|
|
"sender_name": "Ali Ahmadi",
|
|
"sender_email": "ali.ahmadi@example.com",
|
|
"subject": "Problem accessing my purchased course",
|
|
"sender_phone": "+989121234567",
|
|
"message": "Hello, I purchased a course today but the lessons are not available in my account. Please check my access.",
|
|
"created_at": "2026-06-17T09:30:12.421000Z",
|
|
}
|
|
},
|
|
),
|
|
status.HTTP_400_BAD_REQUEST: openapi.Response(
|
|
description="Validation error.",
|
|
examples={
|
|
"application/json": {
|
|
"sender_email": ["Enter a valid email address."],
|
|
"sender_phone": ["The phone number entered is not valid."],
|
|
}
|
|
},
|
|
),
|
|
},
|
|
)
|
|
def post(self, request, *args, **kwargs):
|
|
return super().post(request, *args, **kwargs)
|