Skip to content

Закрыть слабые места MVP: токены доступа, стойкие коды, отмена брони, правила протухания, a11y - #5

Open
devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1783177383-fix-mvp-gaps

Conversation

@devin-ai-integration

Copy link
Copy Markdown

Summary

Закрывает найденные минусы MVP (кроме реальной оплаты Kaspi — это отдельный этап с ключами/хостингом).

Безопасность / доступ

  • Партнёрские эндпоинты (POST /boxes, /partners/{id}/boxes|orders, /redeem, новый POST /partner/boxes/{id}/close) требуют X-Partner-Token; redeem/боксы проверяют владение (require_partner → pid). Токены в колонке partners.token (миграция через PRAGMA table_info + ALTER TABLE), сидятся как pt-p1-demo… (в проде PARTNER_TOKEN_P1 и т.п.). Добавлен GET /partner/me.
  • Админка (/admin/*) требует X-Admin-Token (ADMIN_TOKEN, сравнение через secrets.compare_digest).
  • Коды выдачи: было uuid4().hex[:4] (65k вариантов) → secrets.choice × 6 из алфавита без 0/O/1/I/L (~10^9); при коллизии UNIQUE ретрай с новым кодом внутри той же транзакции (code_factory).

Бизнес-логика

  • Протухшие боксы скрыты из /boxes, бронь на них отклоняется (409); refund возвращает бокс в наличие только пока окно не закрылось.
  • Новое: отмена брони покупателем POST /orders/{code}/cancel (только статус paid, бокс возвращается в продажу) и снятие бокса с продажи партнёром.
  • Валидация: телефон по regex +7 7XX…, окно самовывоза — ISO, конец > начало и не в прошлом.

Фронтенды (обе версии: app/static/index.html и Pages-демо docs/index.html)

  • Ввод/хранение токенов партнёра и админа (backend-версия), кабинет партнёра привязан к токену через /partner/me.
  • Повторный показ QR из «Моих заказов» (GET /orders/{code}/qr), кнопка «Отменить бронь», кнопка «Снять» бокс.
  • Демо на Pages: крипто-коды (crypto.getRandomValues), автопересев если все боксы протухли, фильтр протухших, SRI-хеш на CDN-скрипт qrcode.
  • A11y/качество: закрытие модалки по Esc, role="dialog", автофокус, aria-label, data-*-обработчики вместо inline onclick, консистентный esc(), favicon + description/og-теги.

Тесты: +7 кейсов (бронь протухшего, отмена, отмена после окна, close_box, токены партнёра, refund после окна, ретрай коллизии кода) — 14 passed.

Link to Devin session: https://app.devin.ai/sessions/56d5c80a4b774d5d87cb2c0a92705728
Requested by: @wpalish

…ncel/close, a11y

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
@wpalish wpalish self-assigned this Jul 4, 2026
@devin-ai-integration

Copy link
Copy Markdown
Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration

Copy link
Copy Markdown
Author

Результаты e2e-тестирования

Локальный бэкенд (uvicorn app.main:app, свежая БД + сид), Chrome, весь флоу пройден через UI. Полный отчёт и видео — в Devin-сессии.

  • ✔ Валидация телефона: «123» → тост «Телефон в формате +7 7XX XXX XX XX», заказ не создан; «+7 701 123 45 67» — бронь успешна
  • ✔ Код нового формата SB-3RZGSK (6 символов, алфавит без 0/O/1/I/L)
  • ✔ Повторный QR из «Мои заказы» — тот же код и QR
  • ✔ Отмена брони: тег «отменён», количество восстановлено (осталось 2 → 3)
  • ✔ Токен партнёра: неверный → «Неверный токен партнёра»; pt-p1-demo → кабинет Coffee Point, только свои боксы
  • ✔ Чужой заказ не выдаётся («✕ Этот заказ оформлен в другом заведении»); свой — «✓ Выдано»; повторно — «✕ Этот заказ уже выдан»
  • ✔ «Снять» бокс — исчез из каталога магазина
  • ✔ Токен админа: неверный → «Неверный токен админа», пустая статистика; admin-demo → метрики и заказы; возврат → тег «возврат», GMV пересчитан
  • pytest — 14 passed (протухшие боксы, возврат после окна без рестока, retry коллизии кода)

Не тестировалось: реальная оплата Kaspi Pay (демо), Pages-версия docs/index.html.

Ключевые скриншоты

🔴 Невалидный телефон отклонён 🟢 Бронь с кодом SB-3RZGSK
invalid phone booked
Токены и доступы партнёра/админа
🟢 Кабинет Coffee Point (pt-p1-demo) 🔴 Чужой заказ не выдаётся
partner foreign
🟢 Отмена: qty восстановлен 🟢 Админ: возврат оформлен
restored refund

wpalish pushed a commit that referenced this pull request Jul 14, 2026
PR #6 (+7908/−3565) не мерджится целиком: отведён от 37e0557 (удалил бы
Telegram-уведомления и связку Pages→бэкенд), требует новые обязательные env
и ломает 10-минутный онбординг партнёра (pending+email-верификация+MFA).
Оставлен открытым как банк решений. Отсюда взято лучшее малое, реализовано
своими руками (их код не исполнялся, только читался):

- коды выдачи: 6 символов из криптостойкого алфавита без похожих символов
  (0/O, 1/I/L) — 27^6 ≈ 387 млн комбинаций вместо 65 тыс. у hex-4; легко
  диктовать на кассе (идея дублируется и в Devin PR #5)
- security-заголовки: + Cross-Origin-Opener-Policy, Cross-Origin-Resource-Policy
- Dockerfile: non-root пользователь, PYTHONDONTWRITEBYTECODE/UNBUFFERED,
  YUMMY_DB_PATH=/data по умолчанию
- tools/create_admin.py: bootstrap/повышение админа через getpass (пароль не
  светится в history), promote отзывает все сессии (token_ver+1)
- .github/dependabot.yml: еженедельные обновления pip/actions/docker
- ci/github-actions.yml.example: полный CI (pytest+bandit+CodeQL+trivy+доксборка),
  как example — токен без scope workflow не может пушить воркфлоу

Проверено: 75 тестов зелёные, create_admin смок (created/promoted) на врем. БД.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
wpalish pushed a commit that referenced this pull request Jul 15, 2026
…ние по коду)

Идея «отмена брони» — из Devin PR #5 (код оттуда не брался — база устарела),
реализована под текущую архитектуру:

- POST /orders/cancel {code} — отмена ДО начала окна выдачи: бокс атомарно
  возвращается в продажу, статус cancelled. Владение подтверждает код заказа —
  та же модель доверия, что у /redeem. Повторная отмена/отмена выданного — 409.
- POST /orders/refund {code} — «заказ не выдали», доступен С начала окна.
  ФИКС РЕАЛЬНОГО БАГА: кнопка «Вернуть деньги» звала /admin/refund — в проде
  с YUMMY_ENFORCE_AUTH=1 покупатель получал 401. Теперь самообслуживание.
- UI: «Отменить бронь» до окна / «Вернуть деньги» после начала окна, обе
  обновляют и заказы, и каталог; KZ-переводы всех новых строк.
- Демо-режим (docs/): те же правила в client-store; витрина пересобрана с
  YUMMY_PAGES_API_BASE (живой бэкенд сохранён).
- Оба эндпоинта под rate-limit заказов + аудит-лог с IP.

81 тест (6 новых: до/после окна, повтор, issued, неизвестный код, возврат
остатка). Проверено live: cancel → повтор 409 → refund, новые коды YM-XXXXXX.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
wpalish pushed a commit that referenced this pull request Jul 16, 2026
…арточка); +контраст футера

По UX-ревью:
- #1 localStorage-нотис был плавающей центр-карточкой (читалось как «глюк»
  поверх секций) → тонкая полоса во всю ширину, приклеена к низу (над bottom-nav
  на мобиле, к краю на десктопе). Показ один раз (флаг ym_cookies уже был).
- #6 контраст копирайта/FAQ-текста в футере поднят (.6→.78, .7→.82 opacity) —
  запас под WCAG AA.
Уже было в коде: #4 срочность («🔥 осталось N» + окно выдачи), #7 соцсети на
светлом --on-ink (не бледные). Требуют ассетов (отдельно): #2 превью карты,
#3 логотипы партнёров/отзывы, #5 лого-марка, #8 PWA-бейджи.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wpalish pushed a commit that referenced this pull request Jul 16, 2026
…ив cold-start

- #5 бренд-идентичность: SVG логотип-марка (сюрприз-бокс + листок-росток =
  спасённая еда/эко, бренд-палитра) вместо generic-текста. Применён в хедере,
  футере, онбординге, SVG-favicon (apple-touch остался PNG — iOS не любит SVG).
- #9 cold-start: ci/keep-warm.yml.example — GitHub Actions cron пингует /health
  каждые 10 мин (бесплатно). Как .example (пуш workflow'ов блокирует scope
  токена); либо добавить через GitHub web UI. Надёжный фикс — Render Starter $7.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant