Finishing a lesson or island needs recorded answers and an unlocked island; auto DB backup before migrate; throttles on 15 more routes
- content: POST nodes/{id}/quiz/complete and lessons/{id}/complete refuse a locked island
(403 node_locked, same rule as /api/home) and a user who has not answered every question
(409 answers_missing). Before, only a user with zero hearts and no answers was refused, so
anyone else finished any island, locked or not, and finishing a locked island's lessons
unlocked it. A node or lesson with no questions still passes when unlocked, with no XP.
The client can no longer understate totalQuestions to buy stars.
- throttle_scope on 15 routes that had none (question bank without login, intro, hearts,
notifications, tickets, plans, subscription status, orders, offers, affiliate, assistant
history and feedback, logout).
- admin: OTPCode and Session can only be added/changed by a superuser.
- deploy.sh: integrity-checked DB backup before migrate (keeps the last 2); no migrate without it.
- docs and CLAUDE.md updated to say what is verified and what is not.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
61
deploy.sh
61
deploy.sh
@@ -188,6 +188,67 @@ if [ -d .deploy-backups ]; then
|
||||
fi
|
||||
printf ' دیتابیس و بکاپها فقط برای %s خواندنیاند.\n' "$(id -un)"
|
||||
|
||||
# ─── ۳.۷ بکاپِ دیتابیس پیش از migrate ──────────────────────────────────────
|
||||
# migrate تنها مرحلهای است که داده را برنمیگشتپذیر عوض میکند، پس درست پیش از آن بکاپ
|
||||
# میگیریم؛ تا اینجا بکاپ دستی بود و در یک انتشارِ بیبکاپ هیچ راهی به عقب نبود.
|
||||
#
|
||||
# با sqlite3.Connection.backup() نه cp: کپیِ ساده وسطِ یک نوشتن فایلِ نیمهکاره میدهد (sqlite3
|
||||
# بهعنوانِ فرمان روی سرور نیست). اگر بکاپ نشد، migrate اجرا نمیشود: دیپلوی بیبکاپ را
|
||||
# عمداً نمیپذیریم. فقط ۲ بکاپِ خودکارِ آخر (db-before-deploy-*: همین انتشار و یکی قبلتر) نگه داشته میشود تا دیسکِ
|
||||
# مشترک پر نشود؛ بکاپهای دستی با نامِ دیگر دست نمیخورند.
|
||||
# آزمودهنشده: Postgres (بعد از مهاجرت باید pg_dump جایگزین شود؛ اینجا فقط هشدار میدهد)،
|
||||
# و بکاپ از بیرونِ ماشین که هنوز نیست (بند ۱۷ در چکلیستِ لانچ).
|
||||
step "بکاپِ دیتابیس"
|
||||
python - <<'PYBACKUP' || fail "بکاپِ دیتابیس نشد؛ migrate اجرا نشد و هیچ تغییری روی داده اعمال نشد."
|
||||
import datetime
|
||||
import glob
|
||||
import os
|
||||
import shutil
|
||||
import sqlite3
|
||||
|
||||
import django
|
||||
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
|
||||
django.setup()
|
||||
from django.conf import settings
|
||||
|
||||
db = settings.DATABASES['default']
|
||||
if db['ENGINE'] != 'django.db.backends.sqlite3':
|
||||
print(f" ⚠ موتورِ دیتابیس {db['ENGINE']} است؛ بکاپِ خودکار فقط SQLite را پوشش میدهد. خودتان بکاپ بگیرید.")
|
||||
raise SystemExit(0)
|
||||
|
||||
src_path = str(db['NAME'])
|
||||
if not os.path.exists(src_path):
|
||||
print(' دیتابیس هنوز وجود ندارد (نصبِ تازه)؛ چیزی برای بکاپ نیست.')
|
||||
raise SystemExit(0)
|
||||
|
||||
os.makedirs('.deploy-backups', mode=0o700, exist_ok=True)
|
||||
need = os.path.getsize(src_path) * 2
|
||||
free = shutil.disk_usage('.deploy-backups').free
|
||||
if free < need:
|
||||
raise SystemExit(f' فضای خالی ({free // 2**20} MB) کمتر از دو برابرِ دیتابیس ({need // 2**20} MB) است.')
|
||||
|
||||
dst = '.deploy-backups/db-before-deploy-%s.sqlite3' % datetime.datetime.now().strftime('%Y%m%d-%H%M%S')
|
||||
src, out = sqlite3.connect(src_path), sqlite3.connect(dst)
|
||||
with out:
|
||||
src.backup(out)
|
||||
src.close()
|
||||
out.close()
|
||||
os.chmod(dst, 0o600)
|
||||
check = sqlite3.connect(dst)
|
||||
result = check.execute('PRAGMA integrity_check').fetchone()[0]
|
||||
check.close()
|
||||
if result != 'ok':
|
||||
raise SystemExit(f' بکاپ ساخته شد ولی integrity_check «{result}» داد: {dst}')
|
||||
print(f' {dst} ({os.path.getsize(dst) // 2**20} MB، integrity_check: ok)')
|
||||
|
||||
old = sorted(glob.glob('.deploy-backups/db-before-deploy-*.sqlite3'))[:-2]
|
||||
for path in old:
|
||||
os.remove(path)
|
||||
if old:
|
||||
print(f' {len(old)} بکاپِ خودکارِ قدیمیتر پاک شد (۲ تای آخر میماند).')
|
||||
PYBACKUP
|
||||
|
||||
# ─── ۴. دیتابیس ────────────────────────────────────────────────────────────
|
||||
step "اعمالِ مهاجرتهای دیتابیس"
|
||||
python manage.py migrate --noinput || fail "migrate شکست خورد؛ سرویس ریاستارت نشد."
|
||||
|
||||
Reference in New Issue
Block a user