Harden auth, AI tutor, payments, and subscriptions
This commit is contained in:
@@ -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}"
|
||||
|
||||
Reference in New Issue
Block a user