feat(access): демо-QR — многоразовое приглашение для показа
CI / ruff · mypy · pytest (pull_request) Successful in 1h3m54s

- invites.max_uses/uses: обычное приглашение — 1 вход, демо-QR — до DEMO_INVITE_MAX_USES (50),
  живёт DEMO_INVITE_TTL_HOURS (3 ч); захват атомарный, UPDATE … WHERE uses < max_uses
- users.guest: вошедшие по демо-QR; «🧹 Отключить гостей демо» отзывает всех и гасит демо-QR
- «👥 Участники»: кнопка «🎤 Демо-QR», гости в списке — 🎟
- миграция 0003 (ADD COLUMN без batch — SQLite не пересоздаёт users); проверена на Postgres
- SPEC A-3a, ADR-017, DEMO.md, .env.example

Refs #55

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-22 18:11:53 +03:00
co-authored by Claude Opus 5.5
parent 7a12b3525a
commit e8c3a321a3
14 changed files with 247 additions and 15 deletions
+1
View File
@@ -57,6 +57,7 @@
| A-3 | Приглашение **одноразовое**, живёт 24 ч (настраивается); в БД хранится только хэш токена |
| A-4 | Переход по QR → `/start <token>` → проверка → пользователь становится участником, видит главное меню. Повторное использование, истёкший или неизвестный токен → отказ |
| A-5 | Без приглашения ни одна функция недоступна: проверка — в middleware на **каждом** апдейте (сообщения и нажатия кнопок) |
| A-3a | Исключение для показа (#55): **демо-QR** — одно приглашение на ≤ 50 входов, живёт 3 ч (настраивается). Вошедшие по нему — гости 🎟 (права участника); «Отключить гостей демо» отзывает всех разом и гасит демо-QR |
| A-6 | Защита от перебора: не больше 5 неудачных `/start <token>` в час с одного Telegram ID, дальше — игнор |
| A-7 | Администратор видит участников (имя, кто пригласил, когда, последняя активность) и может **отозвать** доступ — с этого момента апдейты пользователя игнорируются, его черновик удаляется |
| A-8 | Первые администраторы задаются в `.env` (`ADMIN_IDS`); остальные — только через приглашение |