178 lines
16 KiB
Markdown
178 lines
16 KiB
Markdown
# 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`](docs/ROADMAP.md). Результаты проверки интерфейса и пять приоритетных UX-пакетов описаны в [`docs/UI_UX_AUDIT.md`](docs/UI_UX_AUDIT.md).
|
||
|
||
Production-контур для домена `rf4spotter.ru`, TLS, секреты, backup/restore и команды первого запуска описаны в [`deploy/README.md`](deploy/README.md). Он использует отдельный `compose.production.yaml`; локальный `compose.yaml` остаётся средой разработки. Production seed добавляет только справочники — демонстрационные уловы отключены. Изолированные проверки `deploy/test-production-bootstrap.sh` и `deploy/test-backup-restore.sh` подтверждают старт с пустых volumes и восстановление данных.
|
||
|
||
Gitea Actions workflow `.gitea/workflows/ci.yml` на каждый push и pull request проверяет Python, миграции на чистой PostgreSQL, Astro build и полный Compose/Playwright-сценарий. При падении E2E сохраняются логи контейнеров и Playwright-артефакты.
|
||
|
||
Актуальная инвентаризация источников и правила подключения адаптеров находятся в [`docs/data-source-audit.md`](docs/data-source-audit.md). Разрешённый технический пилот RF4DB/RF4-STAT описан в [`docs/community-source-pilot.md`](docs/community-source-pilot.md), а статус разрешений и лимитов — в [`docs/data-permissions.md`](docs/data-permissions.md). Данные сохраняются только в промежуточный staging и не влияют на индекс без явной проверки и публикации администратором.
|
||
|
||
Один ограниченный снимок публичных карточек можно получить исследовательским CLI:
|
||
|
||
```bash
|
||
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, не влияющий на публичную статистику:
|
||
|
||
```bash
|
||
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. Это основной и рекомендуемый сценарий:
|
||
|
||
```bash
|
||
docker compose up --build
|
||
```
|
||
|
||
После успешного запуска:
|
||
|
||
- сайт: <http://localhost:4321>;
|
||
- OpenAPI: <http://localhost:8000/docs>;
|
||
- liveness API: <http://localhost:8000/health>;
|
||
- readiness PostgreSQL, MinIO и импорта: <http://localhost:8000/ready>;
|
||
- консоль MinIO: <http://localhost:9001>.
|
||
|
||
Контейнер 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`.
|
||
|
||
Остановка:
|
||
|
||
```bash
|
||
docker compose down
|
||
```
|
||
|
||
Удаление volume и повторное создание чистой базы — только когда данные больше не нужны:
|
||
|
||
```bash
|
||
docker compose down --volumes
|
||
docker compose up --build
|
||
```
|
||
|
||
Переменные и локальные значения по умолчанию перечислены в [.env.example](.env.example). Секретов в репозитории нет.
|
||
|
||
## Что реализовано
|
||
|
||
- FastAPI и SQLAlchemy 2;
|
||
- PostgreSQL 17 и миграции Alembic до `0009`;
|
||
- идемпотентный 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 и исследовательский парсер:
|
||
|
||
```bash
|
||
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:
|
||
|
||
```bash
|
||
cd apps/web
|
||
npm install
|
||
npm run build
|
||
npm audit --omit=dev
|
||
```
|
||
|
||
E2E после запуска Compose:
|
||
|
||
```bash
|
||
cd apps/web
|
||
npx playwright install chromium
|
||
npm run test:e2e
|
||
```
|
||
|
||
## Импорт официальных рекордов
|
||
|
||
Однократный контейнерный запуск после старта базы:
|
||
|
||
```bash
|
||
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 профилем и по умолчанию опрашивает источник не чаще одного раза в час:
|
||
|
||
```bash
|
||
docker compose --profile scheduler up -d scheduler
|
||
```
|
||
|
||
Обычный `docker compose up` его не запускает. Не включайте профиль во внешнем окружении, пока условия автоматического сбора не согласованы с владельцем источника; отсутствие `robots.txt` не является разрешением.
|
||
|
||
## Пользовательские уловы и модерация
|
||
|
||
Новая запись из `/report` получает статус `pending` и не участвует в активности до одобрения. Административные методы требуют заголовок `Authorization: Bearer $ADMIN_TOKEN`:
|
||
|
||
```bash
|
||
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 заявки и предлагает повторить только загрузку изображения. Повторно отправлять сам улов не требуется.
|
||
|
||
Очередь модерации доступна по адресу <http://localhost:4321/admin/moderation>. Администратор вводит `ADMIN_TOKEN`; интерфейс держит его только в памяти открытой страницы и не сохраняет в URL или браузерном хранилище.
|
||
|
||
Администратор может одобрить, отклонить или удалить сообщение. Удаление очищает ник, комментарий, исходную ссылку и объект скриншота, исключает запись из статистики, но сохраняет обезличенный факт действия в журнале аудита.
|
||
|
||
## Исследовательский парсер официальных рекордов
|
||
|
||
```bash
|
||
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](docs/data-sources.md).
|