Harden auth, AI tutor, payments, and subscriptions

This commit is contained in:
2026-08-22 04:42:36 +03:30
parent f945f960c9
commit cb18c93a3e
41 changed files with 2202 additions and 595 deletions

View File

@@ -50,6 +50,20 @@ class Subscription(models.Model):
started_at = models.DateTimeField(auto_now_add=True)
expires_at = models.DateTimeField(null=True, blank=True)
order_id = models.IntegerField(null=True, blank=True) # ارجاع سبک به payment.Order
# 🔒 اصلاحِ رگرسیونِ concurrency در PurchaseView.post: چون Subscription
# فقط یک ردیف در ازای هر user_id داره، دو درخواستِ هم‌زمانِ خریدِ همون
# کاربر (مثلاً با همون plan) می‌تونن دقیقاً به یک وضعیتِ میانیِ یکسان
# برسن (status='pending', همون plan_id, order_id=None) — یعنی مقایسه‌ی
# مقادیرِ فیلدها (که قبلاً به‌عنوانِ CAS guard در cleanup استفاده می‌شد)
# نمی‌تونه بینِ «ردیفِ نوشته‌شده توسطِ خودِ همین درخواست» و «ردیفِ
# نوشته‌شده توسطِ یک درخواستِ هم‌زمانِ دیگه» تمایز بذاره. version یک
# شمارنده‌ی صریحه که PurchaseView.post هر بار که واقعاً روی این ردیف
# می‌نویسه (با UPDATE اتمیکِ version=F('version')+1، بدونِ نیاز به
# select_for_update/نگه‌داشتنِ قفل) افزایشش می‌ده؛ پس حتی وقتی محتوای
# semantic فیلدهای دیگه بینِ دو نویسنده‌ی هم‌زمان یکسانه، version همیشه
# بینِ نوشته‌های مجزا فرق می‌کنه و می‌تونه به‌عنوانِ CAS token قابل‌اتکا
# برای cleanup استفاده بشه.
version = models.PositiveIntegerField(default=0)
def __str__(self):
return f"sub-user{self.user_id}-{self.status}"