RF4 Spotter

RF4 Spotter — неофициальный сервис свежих точек и статистики клёва для Russian Fishing 4. Он объединяет публичные рекорды и подтверждённые пользовательские уловы, показывает свежесть источников и рассчитывает объяснимые индексы активности и уверенности. Проект не взаимодействует с игровым клиентом и не является официальным продуктом RF4.

Статус разработки

  • этапы 0 и 1 завершены;
  • этап 2, официальный импорт, завершён технически; автоматический профиль остаётся выключенным до явного разрешения владельца источника;
  • этап 3 функционально завершён: форма, раздельные ошибки создания/скриншота с повторной загрузкой, MinIO, модерация, удаление с аудитом и постоянный rate limit готовы;
  • для полного пользовательского сценария добавлен E2E-тест отправка → pending → модерация → публичная статистика;
  • начат этап 4: формула индекса зафиксирована, детерминированные агрегаты и правила включения данных покрыты тестами; далее — сквозная проверка фильтров;
  • RF4DB/RF4-STAT загружаются в изолированный staging; добавлены канонические алиасы и ручная очередь публикации.
  • RF4MAP и RF4 Posts разрешены для исследовательского staging с интервалом не менее 30 минут на источник; CLI обеспечивает cooldown, источники выключены и не публикуются автоматически.

Подробный план и актуальные чекбоксы находятся в docs/ROADMAP.md. Результаты проверки интерфейса и пять приоритетных UX-пакетов описаны в docs/UI_UX_AUDIT.md.

Production-контур для домена rf4spotter.ru, TLS, секреты, backup/restore и команды первого запуска описаны в deploy/README.md. Он использует отдельный compose.production.yaml; локальный compose.yaml остаётся средой разработки. Production seed добавляет только справочники — демонстрационные уловы отключены. Изолированные проверки deploy/test-production-bootstrap.sh и deploy/test-backup-restore.sh подтверждают старт с пустых volumes и восстановление данных.

Политика минимизации данных и ежедневная dry-run-first очистка описаны в docs/data-retention.md.

Host-side мониторинг контейнеров, readiness, диска, резервных копий и TLS описан в docs/production-monitoring.md. Ежедневный systemd timer создаёт проверяемую копию до retention-очистки, а production Compose ограничивает рост JSON-логов контейнеров.

Фактическое состояние DNS/TLS домена и серверный чек-лист ведутся в docs/deployment-status.md. Результаты security review и остаточные ограничения закрытой альфы записаны в docs/security-review.md.

Gitea Actions workflow .gitea/workflows/ci.yml на каждый push и pull request проверяет Python, миграции на чистой PostgreSQL, Astro build и полный Compose/Playwright-сценарий. При падении E2E сохраняются логи контейнеров и Playwright-артефакты.

Актуальная инвентаризация источников и правила подключения адаптеров находятся в docs/data-source-audit.md. Разрешённый технический пилот RF4DB/RF4-STAT описан в docs/community-source-pilot.md, а статус разрешений и лимитов — в docs/data-permissions.md. Данные сохраняются только в промежуточный staging и не влияют на индекс без явной проверки и публикации администратором.

Один ограниченный снимок публичных карточек можно получить исследовательским CLI:

python -m rf4_research.community_cli rf4db --limit 25
python -m rf4_research.community_cli rf4stat-fishing --limit 100
python -m rf4_research.community_cli rf4stat-posts --limit 25
python -m rf4_research.community_cli rf4map-point --url https://rf4map.ru/points/275 --limit 25
python -m rf4_research.community_cli rf4posts-spot --url https://rf4-posts.com/ru/spots/UUID --limit 25

Команды печатают нормализованный JSON в stdout и ничего не записывают в базу. Detail-команды требуют явный публичный URL и не обходят запрещённые /api/. Для RF4-STAT действует пауза не менее пяти секунд между разными страницами; для RF4MAP/RF4 Posts CLI хранит состояние в .cache/community-fetch-state.json и блокирует повтор того же источника раньше 30 минут.

Проверенный JSON можно идемпотентно загрузить в изолированный staging, не влияющий на публичную статистику:

python -m rf4_research.community_cli rf4db --limit 25 \
  | docker compose exec -T api python -m app.cli stage-community-json --input -

Staging проверяет происхождение URL и диапазоны значений. Источники по умолчанию выключены; автоматического преобразования в одобренные уловы нет. Ручная очередь доступна по адресу http://localhost:4321/admin/external-sources. Публикация разрешена только после сопоставления канонических рыбы и водоёма и при наличии координат и веса.

Запуск через Docker

Требуются Docker Engine и Docker Compose. Это основной и рекомендуемый сценарий:

docker compose up --build

После успешного запуска:

Контейнер API сам выполняет alembic upgrade head, затем идемпотентный seed. PostgreSQL хранит данные в именованном volume postgres_data, а MinIO — в minio_data. Compose ожидает readiness PostgreSQL и MinIO перед API, а API-контейнер проверяет /ready. Официальный импорт по умолчанию необязателен; при включённом scheduler установите OFFICIAL_IMPORT_REQUIRED=true, тогда отсутствующий, неуспешный или просроченный запуск сделает readiness отрицательным.

API и scheduler пишут по одной JSON-записи на событие. HTTP-лог содержит только сгенерированный request_id, метод, путь без query string, статус и длительность; IP, заголовок авторизации и пользовательский payload не журналируются. X-Request-ID возвращается клиенту. Стандартный access-log Uvicorn отключён. Уровень управляется LOG_LEVEL.

Остановка:

docker compose down

Удаление volume и повторное создание чистой базы — только когда данные больше не нужны:

docker compose down --volumes
docker compose up --build

Переменные и локальные значения по умолчанию перечислены в .env.example. Секретов в репозитории нет.

Что реализовано

  • FastAPI и SQLAlchemy 2;
  • PostgreSQL 17 и миграции Alembic до 0010;
  • идемпотентный seed с двумя точками и свежими демо-уловами;
  • GET /api/v1/activity с фильтрами периода, водоёма, рыбы, способа и сортировки;
  • GET /api/v1/spots/{id} и /catches;
  • справочники рыб, водоёмов и приманок;
  • Astro SSR-интерфейс с адаптивным дизайном из design-reference без переноса React/Vinext-стека;
  • объяснимые индексы активности и уверенности по формуле спецификации;
  • документированная формула и детерминированные тесты окон 6/12/24/72 часа;
  • состояния «нет данных» и «источник недоступен»;
  • идемпотентный импорт официальных записей с журналом запусков;
  • публичная страница /records с источником и временем последнего импорта;
  • форма /report, защищённые admin API и журнал модерации;
  • отдельные состояния ошибки создания заявки и загрузки скриншота; неудачный скриншот можно добавить повторно по ID и одноразовому секрету уже сохранённой заявки;
  • honeypot и постоянный rate limit в PostgreSQL с HMAC-отпечатками вместо исходных IP;
  • скриншоты уловов в MinIO/S3 с проверкой MIME, расширения, размера и фактического содержимого, повторным кодированием и очисткой метаданных;
  • административная очередь /admin/moderation с одобрением, отклонением и обезличенным удалением записи с аудитом.

Все пользовательские ники и уловы в seed демонстрационные.

Проверка проекта

Backend и исследовательский парсер:

python3 -m venv .venv
.venv/bin/pip install -r apps/api/requirements.txt
.venv/bin/pip install -e .
.venv/bin/pytest -q

Актуальное число тестов выводит команда pytest; набор включает backend, импорт, расчёт активности и исследовательский парсер.

Frontend:

cd apps/web
npm install
npm run build
npm audit --omit=dev

E2E после запуска Compose:

cd apps/web
npx playwright install chromium
npm run test:e2e

Импорт официальных рекордов

Однократный контейнерный запуск после старта базы:

docker compose --profile tools run --rm importer

Импорт делает до трёх ограниченных попыток, проверяет DOM-контракт и не удаляет ранее сохранённые данные при сбое. Повторный запуск обновляет совпавшие записи по SHA-256 ключу и не создаёт дубликаты. Расписание реализовано, но намеренно не включается обычным запуском: сначала требуется согласовать допустимость регулярного опроса официального сайта.

Ручной административный запуск также доступен через POST /api/v1/admin/imports/official-records, журнал — через GET /api/v1/admin/imports. Импорт сохраняет HTTP-метаданные и использует ETag/Last-Modified, когда источник их предоставляет.

Планировщик реализован отдельным opt-in профилем и по умолчанию опрашивает источник не чаще одного раза в час:

docker compose --profile scheduler up -d scheduler

Обычный docker compose up его не запускает. Не включайте профиль во внешнем окружении, пока условия автоматического сбора не согласованы с владельцем источника; отсутствие robots.txt не является разрешением.

Пользовательские уловы и модерация

Новая запись из /report получает статус pending и не участвует в активности до одобрения. Административные методы требуют заголовок Authorization: Bearer $ADMIN_TOKEN:

curl -H "Authorization: Bearer change-me-in-production" \
  "http://localhost:8000/api/v1/admin/catch-reports?status=pending"

Перед внешним развёртыванием обязательно замените демонстрационные ADMIN_TOKEN, RATE_LIMIT_SECRET, S3_ACCESS_KEY и S3_SECRET_KEY. Форма принимает JPEG, PNG и WebP до 8 МБ; API сверяет MIME и расширение с фактическим форматом, повторно кодирует изображение и удаляет EXIF перед сохранением в MinIO. Модератор получает временную подписанную ссылку через admin API.

Если создание записи прошло успешно, а загрузка скриншота завершилась ошибкой, форма сохраняет на один час ID заявки и одноразовый секрет в защищённой HttpOnly cookie и предлагает повторить только загрузку изображения. Повторно отправлять сам улов не требуется; один UUID заявки не даёт права изменить чужую запись.

Очередь модерации доступна по адресу http://localhost:4321/admin/moderation. Администратор вводит ADMIN_TOKEN; интерфейс держит его только в памяти открытой страницы и не сохраняет в URL или браузерном хранилище.

Администратор может одобрить, отклонить или удалить сообщение. Удаление очищает ник, комментарий, исходную ссылку и объект скриншота, исключает запись из статистики, но сохраняет обезличенный факт действия в журнале аудита.

Исследовательский парсер официальных рекордов

python -m rf4_research.records \
  --url https://rf4game.de/records/region/RU/ \
  --region RU \
  --category records

Команда делает один HTTP-запрос и печатает типизированные записи в JSON. Исследовательский и продуктивный адаптеры используют общий fail-closed DOM-парсер rf4_research/official_parser.py; продуктивный слой добавляет ограниченные повторы, условные HTTP-запросы, транзакции, дедупликацию и журнал запусков. Подтверждённая структура источника и риски описаны в docs/data-sources.md.

S
Description
No description provided
Readme
107 MiB
Languages
Python 46.6%
TypeScript 29.1%
Astro 16.5%
CSS 3.7%
Shell 3.3%
Other 0.7%