type — бесконечная лента слов в формате ТикТок для подготовки к ЕГЭ по русскому языку. В каждом слове есть один пропуск, который нужно правильно заполнить одним из вариантов внизу экрана.
- @kostya112221 — добавление заданий с паронимами
- @MamaKupiSnikers — помощь с доработкой фронта в разделе паронимов
Для локального запуска нужны Docker Engine и Docker Compose. Стек включает Gunicorn-приложение, PostgreSQL и Redis:
docker compose up --build -d
docker compose psПриложение будет доступно по адресу http://localhost:8000. Перед запуском
Gunicorn отдельный одноразовый сервис migrate дожидается PostgreSQL и
применяет Alembic-миграции; web-контейнер запускается только после его успешного
завершения. Состояние сервисов
можно проверить отдельно:
curl --fail http://localhost:8000/health/live
curl --fail http://localhost:8000/health/readyПервичное наполнение тестовыми заданиями выполняется один раз:
docker compose exec app flask --app app csv_to_db fixtures/test_words.csvЛоги и остановка стека:
docker compose logs -f app
docker compose downPostgreSQL и Redis используют именованные volumes, поэтому обычный
docker compose down не удаляет данные. Команда docker compose down -v
удалит оба volume без возможности восстановления.
Публичное развёртывание использует Apache reverse proxy на отдельной машине и
приватный origin-сервер приложения. Русскоязычная инструкция, которую можно
выполнять по шагам: запуск Type в production.
Дополнительные замечания по переносу данных находятся в
docs/deployment.md. Локальный compose.override.yaml
на сервере не подключается. Для локального запуска reverse proxy не нужен;
публичный production требует HTTPS reverse proxy или load balancer.
Параметры локального Docker-стека можно переопределить в .env:
APP_PORT=8000
POSTGRES_DB=type
POSTGRES_USER=type
POSTGRES_PASSWORD=type-local
DOCKER_SECRET_KEY=replace-with-at-least-32-random-characters
DOCKER_PUBLIC_URL=http://localhost:8000
DOCKER_TRUSTED_HOSTS=localhost,127.0.0.1
DOCKER_YANDEX_REDIRECT_URI=http://localhost:8000/auth/yandex/callbackЗначения по умолчанию предназначены только для локальной машины. Перед публичным развёртыванием задайте уникальные секреты, HTTPS URL и параметры доверенного reverse proxy.
Импорт требует одинаковой актуальной Alembic-ревизии у источника и цели и отказывается изменять PostgreSQL, если в нём уже есть данные. На время переноса остановите основной контейнер приложения:
docker compose stop app
docker compose run --rm --user root \
--volume "$(pwd)/instance/app.db:/tmp/source.db:ro" \
app flask --app app sqlite_to_postgres /tmp/source.db
docker compose up -d appКоманда переносит данные одной транзакцией и синхронизирует PostgreSQL sequences. Исходный SQLite-файл подключается в контейнер только для чтения.
docker compose exec -T postgres pg_dump -U type -d type > type-backup.sql
docker compose exec -T postgres psql -U type -d type < type-backup.sqlПри изменённых POSTGRES_USER и POSTGRES_DB подставьте соответствующие
значения в команды.
- Клонируйте репозиторий:
git clone https://github.com/eledays/type.git
- Перейдите в директорию проекта:
cd type
- Установите зависимости:
python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt - Настройте
.envВ production (SECRET_KEY=replace-with-at-least-32-random-characters DATABASE_URL=sqlite:///app.db FLASK_PORT=5000 MAX_CONTENT_LENGTH=65536 COOKIE_SAMESITE=Lax REMEMBER_COOKIE_DAYS=30 YANDEX_CLIENT_ID=your-client-id YANDEX_CLIENT_SECRET=your-client-secret YANDEX_REDIRECT_URI=http://localhost:5000/auth/yandex/callback LEGAL_OPERATOR_NAME=ФИО или наименование оператора LEGAL_CONTACT_EMAIL=privacy@example.com ANONYMOUS_ACTION_LIMIT=30 ANONYMOUS_RETENTION_DAYS=90 ANALYTICS_CACHE_SECONDS=60 PRACTICE_CARD_BATCH_SIZE=3 PRACTICE_CARD_BATCH_MAX=12 PRACTICE_DIFFICULT_CANDIDATE_LIMIT=50 PRACTICE_SWIPE_GRACE_STRIKE=3 RATE_LIMIT_STORAGE_URI=memory://DEBUG=false) задайте общий backend rate limiter, напримерRATE_LIMIT_STORAGE_URI=redis://localhost:6379/0. - Примените миграции базы данных:
flask --app app db upgrade
- Заполните базу небольшим набором слов для разработки:
flask --app app csv_to_db fixtures/test_words.csv
Проверить и удалить анонимные профили, неактивные дольше настроенного срока:
```bash
flask --app app cleanup_anonymous --dry-run
flask --app app cleanup_anonymous
- Запустите проект:
python run_dev.py
После изменения клиентского кода обновите минифицированную сборку:
npm ci
npm test
npm run build
npm run check:staticПрямые production-зависимости задаются в requirements.in, а полностью
зафиксированный граф с хешами хранится в requirements.txt. При плановом
обновлении установите pip-tools==7.5.2, измените requirements.in и
пересоберите lock-файл:
pip-compile --generate-hashes --allow-unsafe --strip-extras \
--no-annotate --no-header --output-file requirements.txt requirements.inProduction-образ устанавливает зависимости с --require-hashes.
Основной набор (SQLite и тест браузерной логики через Node.js):
pip install -r requirements-test.txt
pytestПолный набор локальных quality gates:
pip install -r requirements-audit.txt
ruff check .
mypy app parsing/parse.py parsing/to_csv.py parsing_paronyms/parser_sentence.py
coverage run -m pytest
coverage reportPostgreSQL-проверка намеренно принимает только отдельную базу с именем,
оканчивающимся на _test, и очищает её:
ALLOW_POSTGRES_TESTS=1 \
TEST_POSTGRES_URL=postgresql+psycopg://type:type-test@localhost/type_test \
pytest -m postgresАудит Python-зависимостей и статический анализ безопасности:
pip install -r requirements-audit.txt
scripts/check_security.shЗависимости одноразовых скриптов сбора исходных данных устанавливаются отдельно и не входят в production-образ:
pip install -r requirements-scraping.txtСкрипты сбора данных запускаются только вручную и требуют явных путей, поэтому импорт модулей не открывает браузер и не перезаписывает файлы:
python parsing/parse.py SOURCE.txt RESOLVED.txt ERRORS.txt
python parsing/to_csv.py SOURCE_DIRECTORY words.csv
python parsing_paronyms/parser_sentence.py sentence.txt --url TEST_URLПеред релизом отдельно примените миграции и запустите PostgreSQL-проверки на
той же основной версии PostgreSQL, которая используется в production. В
production Compose миграции выполняет одноразовый сервис migrate, и app
запускается только после его успешного завершения.
Соглашения по структуре blueprint, URL, API и совместимости описаны в
docs/routing.md.
Все задания имеют общий идентификатор в PracticeItem. Специфичные данные
хранятся в SpellingExercise и ParonymExercise, а пользовательские действия
ссылаются только на practice_item_id. API-типы этих упражнений — spelling
и paronym.
flask --app app csv_to_db path/to/words.csv
flask --app app txt_to_db path/to/paronyms.txt
flask --app app sentence_to_db path/to/sentences.txtФормат строк орфографического CSV:
слово_с_пропуском;правильный_ответ;вариант1,вариант2;категория.
Правильный ответ указывается отдельно и должен входить в список вариантов.
В том же CSV можно описывать полноценные упражнения на паронимы:
paronym;предложение_с_______;правильный_пароним;пароним1,пароним2;word_tags.
Импорт создаёт или дополняет группу паронимов и связывает с ней упражнение.
Перед импортом база должна быть обновлена командой flask --app app db upgrade.
Условия использования, политика обработки данных и отдельное согласие
публикуются по адресам /legal/terms, /legal/privacy и
/legal/personal-data-consent. До принятия текущих версий HTML-запросы
перенаправляются на /legal/consent, а API отвечает кодом 403. Принятые
версии и время фиксируются в legal_acceptance; после изменения документа
обновите соответствующую константу версии в app/services/legal.py, чтобы
запросить согласие повторно.
Перед публичным запуском укажите настоящие LEGAL_OPERATOR_* реквизиты,
разместите базы персональных данных граждан РФ в России и выполните внешние
организационные обязанности оператора, включая применимое уведомление
Роскомнадзора. Тексты должны быть проверены юристом с учётом реального
владельца, инфраструктуры и процессов проекта.
Подробное описание каждой переменной и примеры приведены в
docs/legal-configuration.md.
Если у вас уже есть база, созданная до перехода на Flask-Migrate, сначала сделайте её резервную копию. Затем отметьте baseline и примените миграции:
flask --app app db stamp 5f01b47acedc
flask --app app db upgradeПри миграции старые user_id сохраняются в User.telegram_id. Внутренний User.id создаётся отдельно и автоматически.
Создайте на oauth.yandex.ru приложение типа «Для авторизации пользователей»
и включите права «Логин, имя и фамилия, пол» и «Портрет пользователя».
Callback URL должен в точности совпадать с
YANDEX_REDIRECT_URI. По умолчанию анонимный пользователь может совершить 30
действий; лимит меняется через ANONYMOUS_ACTION_LIMIT. При последующем входе
его ответы и пропуски автоматически переносятся в Яндекс-профиль.
Размер фоновой подгрузки ленты задаётся через PRACTICE_CARD_BATCH_SIZE, а
верхняя граница параметра API limit — через PRACTICE_CARD_BATCH_MAX.
Размер пула сложных кандидатов и длина серии без подтверждения пропуска
настраиваются через PRACTICE_DIFFICULT_CANDIDATE_LIMIT и
PRACTICE_SWIPE_GRACE_STRIKE.
В общей ленте орфографические и паронимические упражнения выбираются из
единого набора PracticeItem по одинаковым правилам.
Все маршруты ограничены по частоте. Для OAuth, изменяющих запросов и отправки
сообщений действуют дополнительные, более строгие лимиты. Значения настраиваются
переменными RATE_LIMIT_DEFAULT, RATE_LIMIT_APPLICATION,
RATE_LIMIT_AUTH, RATE_LIMIT_MUTATION, RATE_LIMIT_REPORT и
RATE_LIMIT_ANALYTICS_SEARCH.
Локально счётчики хранятся в памяти процесса. При запуске нескольких Gunicorn
worker-ов задайте общее хранилище, например
RATE_LIMIT_STORAGE_URI=redis://localhost:6379/0.
Если Flask находится за reverse proxy, укажите точное число доверенных прокси
в TRUSTED_PROXY_COUNT; не включайте доверие к X-Forwarded-For без прокси.
ID текущего пользователя показан на странице настроек. Выдать ему права администратора:
UPDATE "user" SET is_admin = TRUE WHERE id = <USER_ID>;Отозвать права:
UPDATE "user" SET is_admin = FALSE WHERE id = <USER_ID>;