public user IDs, plans priced by tutor cost, age-based tutor tone, self-service account deletion
- profile publicId ("User482917", accounts/public_id.py); admin shows and searches it
- plans API reports aiDailyQuota per plan and freeAiDailyQuota; migration 0007 sets the
temporary prices (monthly 99,000 / yearly 990,000 toman, 5 tutor questions a day) unless
an admin already edited the yearly plan
- tutor tone follows the learner's age (18+ gets an adult tone at any grade); notation
still follows the grade's book; answer cache keyed by tone (prompt tutor-v12-age-tone)
- POST /api/user/delete: phone users confirm with a fresh SMS code (same checks as login,
now shared in verify_phone_otp), guests type "حذف"; payment records are kept, demo
accounts refused
- delete_users --skip-paid
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
32
accounts/public_id.py
Normal file
32
accounts/public_id.py
Normal file
@@ -0,0 +1,32 @@
|
||||
"""
|
||||
شناسهی نمایشیِ کاربر («User482917») بهجای شمارهی ردیفِ دیتابیس.
|
||||
|
||||
شناسهی خام («۱۰») هم کوچک و بیاعتبار دیده میشد، هم میگفت اپ چند کاربر دارد. این شکل
|
||||
ششرقمی و ثابت است و از خودِ id ساخته میشود، پس ستونی در دیتابیس لازم ندارد:
|
||||
|
||||
code = 100000 + (id × 7919) mod 900000
|
||||
|
||||
۷۹۱۹ با ۹۰۰۰۰۰ هیچ مقسومِ علیهِ مشترکی ندارد، پس تا ۹۰۰ هزار کاربر هیچ دو کاربری یک کد
|
||||
نمیگیرند و کد به id برمیگردد (`decode`)؛ پنلِ ادمین با همین کاربر را پیدا میکند. این رمز
|
||||
نیست: هر کس فرمول را بداند id را درمیآورد. فقط برای نمایش و پشتیبانی است، نه دسترسی.
|
||||
"""
|
||||
import re
|
||||
|
||||
PREFIX = 'User'
|
||||
_MOD = 900_000
|
||||
_MUL = 7919
|
||||
_INV = pow(_MUL, -1, _MOD)
|
||||
_PATTERN = re.compile(rf'^\s*(?:{PREFIX})?\s*(\d{{6}})\s*$', re.IGNORECASE)
|
||||
|
||||
|
||||
def encode(user_id: int) -> str:
|
||||
return f'{PREFIX}{100_000 + (user_id * _MUL) % _MOD}'
|
||||
|
||||
|
||||
def decode(code: str) -> int | None:
|
||||
"""«User482917» یا «482917» → id؛ None اگر شکلش این نیست."""
|
||||
match = _PATTERN.match(code or '')
|
||||
if not match:
|
||||
return None
|
||||
user_id = ((int(match.group(1)) - 100_000) * _INV) % _MOD
|
||||
return user_id or None
|
||||
Reference in New Issue
Block a user