صفحههای فرانت خوانده شدند و با API مقایسه شدند. ساختارِ ما از کتابِ واقعی
درآمده و درستتر است، پس مدل عوض نشد — ولی چند چیز کم داشت.
مهمترینش یک باگ بود: /api/home پارامتر را `grade` میخواند و کلاینتی که به
فرانت فرستادیم `gradeId` میفرستد. پارامترِ ناشناخته بیصدا نادیده گرفته
میشد، یعنی کاربر پایهای را انتخاب میکرد و نقشهی پایهی دیگری میدید
بدونِ هیچ خطایی. حالا هر دو نام پذیرفته میشوند (`gradeId` نامِ درست است،
با بقیهی API یکی). تستِ سرتاسریِ قبلی این را نگرفته بود چون home را بدونِ
آرگومان صدا زده بود.
/api/grades حالا lessonCount میدهد. فهرستِ پایههای فرانت تا دوازدهم است
ولی محتوای ما تا نهم؛ بدونِ این عدد، کاربر پایهی خالی را انتخاب میکند و
صفحهی خالی میبیند بیآنکه بفهمد چرا.
GET /api/nodes/{id}/lessons اضافه شد. /api/home فقط شناسهی درسها را
میدهد، ولی نقشهی مهارت برای هر قدم عنوان، نوع، زیرنویس و «انجام شد» لازم
دارد — بدونش فرانت باید برای هر درس یک intro جدا میگرفت.
Lesson حالا kind (آزمون/ویدیو) و متنِ سهتکه دارد: توضیح، مثال، حلِ
گامبهگام. ویدیو از قبل سوالِ بازِ محصول بود و فرانت آیتمش را دارد؛ حالا
API میتواند بیانش کند، حتی اگر محتوایش بعداً بیاید. Unit یک blurb گرفت و
Node آیکون و رنگ، تا فرانت رنگها را هاردکد نکند. هیچکدام اجباری نیست، پس
محتوای موجود دستنخورده کار میکند.
PATCH /api/user/profile حالا age را هم میپذیرد (صفحهی ثبتنامِ فرانت سن
میپرسد). ذخیرهسازی همچنان birthYear است چون کهنه نمیشود؛ این فقط ورودی
را ترجمه میکند تا دو طرف حسابِ متفاوت درنیاورند.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
129 lines
5.1 KiB
Python
129 lines
5.1 KiB
Python
from rest_framework import serializers
|
||
|
||
|
||
# ---- Request serializers ----
|
||
|
||
class GuestSessionRequestSerializer(serializers.Serializer):
|
||
type = serializers.ChoiceField(choices=['guest'])
|
||
deviceId = serializers.CharField(max_length=64)
|
||
|
||
|
||
class LoginRequestSerializer(serializers.Serializer):
|
||
phone = serializers.CharField(max_length=15)
|
||
otp = serializers.CharField(max_length=6)
|
||
|
||
|
||
class RefreshRequestSerializer(serializers.Serializer):
|
||
refreshToken = serializers.CharField()
|
||
|
||
|
||
class SetGradeRequestSerializer(serializers.Serializer):
|
||
gradeId = serializers.IntegerField()
|
||
|
||
|
||
def _jalali_year_now() -> int:
|
||
"""
|
||
سالِ جاریِ شمسی. تبدیلِ سن به سالِ تولد بدونِ این، سالِ میلادی میداد و
|
||
اعتبارسنجیِ birthYear (بازهی ۱۳۰۰ تا ۱۵۰۰) ردش میکرد.
|
||
"""
|
||
from django.utils import timezone
|
||
|
||
today = timezone.localdate()
|
||
# فروردین ۱ برابرِ ۲۱ مارس است؛ پیش از آن هنوز سالِ شمسیِ قبلی است.
|
||
return today.year - 622 if (today.month, today.day) >= (3, 21) else today.year - 623
|
||
|
||
|
||
class UpdateProfileRequestSerializer(serializers.Serializer):
|
||
"""
|
||
ورودی PATCH /api/user/profile — همهی فیلدها اختیاریاند و فقط همانهایی
|
||
که فرستاده شوند تغییر میکنند (partial update واقعی).
|
||
|
||
`phone` و `isGuest` عمداً اینجا نیستند: شماره فقط از مسیرِ OTP عوض
|
||
میشود و مهمانبودن را کاربر نباید بتواند خودش تغییر دهد.
|
||
"""
|
||
name = serializers.CharField(max_length=100, required=False, allow_blank=True)
|
||
avatar = serializers.CharField(max_length=100, required=False, allow_blank=True)
|
||
birthYear = serializers.IntegerField(required=False, allow_null=True,
|
||
min_value=1300, max_value=1500)
|
||
parentPhone = serializers.CharField(max_length=15, required=False, allow_blank=True)
|
||
bio = serializers.CharField(max_length=200, required=False, allow_blank=True)
|
||
# صفحهی ثبتنامِ فرانت سن میپرسد، نه سالِ تولد. ذخیرهسازی همچنان
|
||
# birthYear است چون کهنه نمیشود؛ این فقط ورودی را ترجمه میکند تا فرانت
|
||
# مجبور نباشد خودش تبدیل کند و دو طرف حسابِ متفاوت درنیاورند.
|
||
age = serializers.IntegerField(required=False, allow_null=True,
|
||
min_value=5, max_value=99, write_only=True)
|
||
|
||
def validate(self, attrs):
|
||
if not attrs:
|
||
raise serializers.ValidationError('حداقل یکی از فیلدها باید ارسال شود.')
|
||
|
||
age = attrs.pop('age', None)
|
||
if age is not None:
|
||
if attrs.get('birthYear') is not None:
|
||
raise serializers.ValidationError(
|
||
'یکی از age یا birthYear را بفرستید، نه هر دو را.',
|
||
)
|
||
attrs['birthYear'] = _jalali_year_now() - age
|
||
return attrs
|
||
|
||
|
||
class RequestOTPRequestSerializer(serializers.Serializer):
|
||
"""اپ کمکی برای گرفتن OTP (در سند اصلی نبود، برای تستپذیری Swagger اضافه شد)."""
|
||
phone = serializers.CharField(max_length=15)
|
||
|
||
|
||
# ---- Response serializers ----
|
||
|
||
class GuestSessionResponseSerializer(serializers.Serializer):
|
||
userId = serializers.CharField()
|
||
token = serializers.CharField()
|
||
isNewUser = serializers.BooleanField()
|
||
isGuest = serializers.BooleanField()
|
||
|
||
|
||
class LoginResponseSerializer(serializers.Serializer):
|
||
userId = serializers.CharField()
|
||
token = serializers.CharField()
|
||
refreshToken = serializers.CharField()
|
||
|
||
|
||
class RefreshResponseSerializer(serializers.Serializer):
|
||
token = serializers.CharField()
|
||
refreshToken = serializers.CharField()
|
||
expiresIn = serializers.IntegerField()
|
||
|
||
|
||
class GradeSerializer(serializers.Serializer):
|
||
id = serializers.IntegerField()
|
||
title = serializers.CharField()
|
||
icon = serializers.CharField()
|
||
color = serializers.CharField()
|
||
|
||
|
||
class GradesListResponseSerializer(serializers.Serializer):
|
||
grades = GradeSerializer(many=True)
|
||
|
||
|
||
class SetGradeResponseSerializer(serializers.Serializer):
|
||
success = serializers.BooleanField()
|
||
gradeId = serializers.IntegerField()
|
||
|
||
|
||
class RequestOTPResponseSerializer(serializers.Serializer):
|
||
success = serializers.BooleanField()
|
||
devOtp = serializers.CharField()
|
||
note = serializers.CharField()
|
||
|
||
|
||
class ProfileResponseSerializer(serializers.Serializer):
|
||
"""پاسخ GET /api/user/profile — فقط دادهی لازم برای تب Profile اپ موبایل."""
|
||
userId = serializers.CharField()
|
||
phone = serializers.CharField(allow_null=True)
|
||
name = serializers.CharField()
|
||
avatar = serializers.CharField()
|
||
isGuest = serializers.BooleanField()
|
||
gradeId = serializers.IntegerField(allow_null=True)
|
||
birthYear = serializers.IntegerField(allow_null=True)
|
||
parentPhone = serializers.CharField()
|
||
bio = serializers.CharField()
|