Files
rf4-spotter/docs/PROJECT_AUDIT_2026-09-08.md
ik c6fdc969c2
CI / backend-and-migrations (push) Canceled after 0s
CI / astro-build (push) Canceled after 0s
CI / compose-e2e (push) Canceled after 0s
fix: separate admin page and API authentication
2026-09-09 07:29:39 +07:00

27 KiB
Raw Permalink Blame History

Аудит RF4 Spotter — 8 сентября 2026

Обновление 9 сентября: T01 исправлен (bb61d21), T02 закрыт разделением Basic для страниц и Bearer для API; оба проверены скриптом deploy/test-proxy-routing.sh. Ниже сохранены исходные находки аудита; актуальные отметки выполнения — в ROADMAP. T03 остаётся блокером production.

База проверки: commit 486b4e9. Аудит охватывает production-конфигурацию, API, импорт и публикацию данных, Astro, пользовательские сценарии, визуальную идентичность и SEO. Это отчёт о текущем состоянии, а не подтверждение готовности к запуску. Исправления приложения в этот пакет не входят.

Вывод

Стек соответствует продукту: Astro SSR + FastAPI + PostgreSQL + MinIO + Caddy. Перенос на Next.js/Vinext, добавление SPA-фреймворка, Redis или микросервисов сейчас не обоснованы. Есть полезная основа: миграции, модерация, provenance, проверка изображений, Docker, backup/restore, SEO-разметка и выразительный рыболовный визуальный язык.

Открывать production пока рано. Подтверждены три блокирующих противоречия конфигурации: маршрутизация формы, несовместимые схемы admin-аутентификации и отсутствие внешней сети у community scheduler. Успешная локальная проверка напрямую через Astro/API эти дефекты не обнаруживает. Главный продуктовый риск — фактическое наполнение и достоверность времени/оценок, а не недостаток декоративных элементов.

Метод и границы

  • Прочитаны конфигурации Compose/Caddy/Docker/CI, ключевые обработчики API и Astro, код агрегации, scheduler, research CLI, staging/review/retention, seed, компоненты и стили, текущие планы и ограничения.
  • Встроенный браузер: главная текущего локального стенда, desktop 1280×1000 и mobile 390×844; оценены композиция, меню и фильтры. Предыдущий прогон 320/390/768/1280 проверял прежде всего переполнение. Его нельзя считать полноценной визуальной приёмкой всех состояний.
  • astro check: 40 файлов, 0 ошибок, 0 предупреждений. Целевые тесты CLI/scheduler/activity: 16 passed. Общий pytest был начат, но завершённый результат в этом прогоне не получен; прежние общие результаты не выдаются за новый полный прогон.
  • Внешние парсеры не запускались, 30-минутный лимит не расходован. Актуальность HTML источников, CVE всех зависимостей, нагрузка, TLS/DNS, реальный backup на сервере и поисковая индексация повторно не проверялись.
  • Проверены официальные документы Docker, Caddy и Google. Подтверждение по коду отделено ниже от дизайнерских предложений и будущих проверок.

Приоритеты: P0 — блокирует production; P1 — нужно до открытой альфы; P2 — улучшение качества/масштабирования; P3 — после обратной связи пилота.

Техническая часть и эксплуатация

ID Приоритет Подтверждение и последствие Критерий исправления
T01 P0 deploy/Caddyfile: /api/* уходит в FastAPI, но /api/report и /api/report-screenshot реализованы в Astro. Production-форма попадает в отсутствующий маршрут. Через Caddy отправка и повтор загрузки достигают Astro и возвращают ожидаемый 303; /api/v1/* достигает FastAPI.
T02 P0 Caddy basic_auth на /api/v1/admin/* требует Basic в Authorization; admin JS посылает Bearer, _admin требует именно Bearer. Два слоя используют один несовместимый контракт. Выбрать согласованную схему; доказать успешные чтение/модерацию через proxy и отказ неавторизованному клиенту.
T03 P0 compose.production.yaml: community-scheduler только в backend, сеть internal: true. Нет внешней сети для HTTPS источников. Разрешён исходящий доступ scheduler; БД/MinIO остаются без внешних портов. Проверить локальным контролируемым HTTP-источником.
T04 P1 Astro POST не передаёт идентичность клиента, FastAPI rate limit использует request.client.host. Заявки через web объединяются под его адресом. Доверенные proxy явно не настроены. Два клиента имеют независимый лимит; поддельный forwarded header его не обходит; проверка через всю цепочку Caddy → Astro → API.
T05 P1 В Astro request.formData() выполняется до try, POST fetch без timeout; Caddy не задаёт лимит тела. Лимит 8 МБ в Python применяется после приёма multipart. Ограничить входной body до буферизации, обработать повреждённый multipart, задать время ожидания и понятные 413/429/ошибки формы.
T06 P1 Bootstrap запускает db/minio/api/web, без proxy и community-scheduler; CI проверяет локальный Compose. monitor.sh не включает community-scheduler, /ready знает лишь официальный импорт. Один production-contract прогон через proxy, проверка scheduler heartbeat/последнего успеха и уведомление о зависании/остановке.
T07 P1 GET /api/v1/imports публично возвращает ImportRunOut, включая error_summary и source_url. Публичный DTO содержит только необходимые безопасные статусы; детали ошибок доступны администратору.
T08 P2 Python фиксирует прямые зависимости, но не транзитивные; Docker base tags изменяемы. В CI нет web unit job (test:unit) и отдельного dependency audit. Воспроизводимый Python lock/constraints, поддерживаемые runtime-версии и регулярная проверка зависимостей; подключить существующие unit-тесты. CVE здесь не утверждаются.
T09 P2 activity_rows загружает все уловы окна и агрегирует Python-списками до пагинации; на карточке точки также читается история. Public cache локален процессу. Измерить память/p95 на представительном объёме; оптимизировать запросы по результату. Сохранить документированный TTL 20 с до необходимости общей инвалидации.
T10 P2 API/web стартуют с миграциями/seed; MinIO app policy readwrite шире одного bucket, общие admin credentials. Оформить отдельный release/migration шаг, bucket policy и план персонального доступа при росте команды; не добавлять identity provider до потребности.

Семантика изоляции подтверждена Docker networks. Конфликт заголовков T02 следует из кода и контракта Caddy Basic Auth.

Парсеры и достоверность данных

ID Приоритет Подтверждение и последствие Критерий исправления
D01 P1 oldest_site_source включает выключенные адаптеры. Если самым старым RF4-STAT endpoint окажется disabled, включённый сосед будет постоянно пропускаться. Ротация только enabled-кандидатов; тест выключенного соседа, включения обратно, ошибки и двух конкурирующих запусков.
D02 P1 Research CLI делает check/write раздельно без межпроцессного lock, повреждённый state трактует как пустой; старые source-key записи после перехода к доменам не учитываются. Автономный state не связан с PostgreSQL. Атомарное резервирование с fail-closed, миграция состояния; production сбор через единый механизм, offline parsing отделён. Нельзя заявлять общую гарантию только на основании предупреждения README.
D03 P1 fetch_html допускает произвольный override URL, автоматические redirects, неограниченный read(). fetch_site_key различает download.rf4db.com и rf4db.com. Единый реестр разрешённых площадок/хостов, проверка URL и redirects до HTTP, лимит ответа, корректные 429/Retry-After в рамках ≥30 минут.
D04 P1 RF4-STAT fishing выдаёт waterbody_external_id=None. Mapping сохраняет fallback alias по имени, но _auto_publish требует оба external ID. Ранее подтверждённое имя не включает автопубликацию следующей полной записи. Использовать единый безопасный контракт алиасов для suggestion/manual/auto; не разрешать неподтверждённое fuzzy-сопоставление.
D05 P1 Scheduler RF4MAP/RF4 Posts опрашивает по одному фиксированному detail URL. Seed даёт только 2 рыбы/2 водоёма; на локальной главной также только они и 0 активных точек. «Все парсеры включены» не означает полный охват. Реестр задач/очередь разрешённых URL и пополнение канонического каталога; видимые счётчики fetched/staged/mapped/published/age; приёмочный набор из каждого адаптера.
D06 P1 Score зависит от количества сообщений одного ника; текст главной «Один игрок не может искусственно поднять уверенность» неверен. Имена без аккаунтов не доказывают независимость. Исправить обещание, определить ограничения вклада одного игрока и неизвестных авторов; воспроизводимый пример спама не изображает независимые подтверждения.
D07 P1 В activity окно/затухание по reported_at, подпись свежести по caught_at or reported_at; community publisher записывает published_at также в caught_at. В feed «Получено» — last_seen_at, который обновляется каждым опросом. Разделить время улова, публикации, первого получения и последнего обнаружения; старый повторно найденный материал не выглядит новым уловом.
D08 P1 На /spots/[id] score берётся из первых 100 глобальных activity-строк и только по spot_id, хотя индекс рассчитан на spot+fish. Возможны ложное «данных нет» и оценка одной рыбы под видом всей точки. Прямой запрос оценки точки с явным контекстом рыбы; тест >100 комбинаций и нескольких рыб на точке.
D09 P2 Staging и autopublish коммитятся отдельными операциями; ошибка поздней записи может оставить часть публикаций при failed journal. Изменение сравнивает весь payload; полная история редакций отсутствует. Явная семантика partial success, идемпотентный retry, сравнение значимых полей и история редакций/удалений; отсутствие записи в очередном списке само по себе не означает удаление.
D10 P2 source_status анализирует последние 20 запусков, running может считаться healthy; backoff смешан для домена, успешный сосед может сбросить ошибки endpoint. Раздельные domain cooldown и endpoint backoff, stalled/running статус, последний успех вне ограничения 20 строк.

Обнаруженная особенность sitemap не является ошибкой пагинации: API public-spot-pages выдаёт два пути на строку БД, поэтому limit=500 и остановка при <1000 путях согласованы. Нужен контрактный тест и явная документация, а не механическая замена на <500.

UI/UX

ID Приоритет Наблюдение Критерий/решение
U01 P1 На screenshot 1280×1000 период и сортировка стоят вертикально; фильтры занимают около 196 px. Используется details/display:contents, результат не соответствует пятиколоночной задумке. Устойчивая desktop-сетка и мобильное раскрытие, проверка реального layout браузеров; отсутствие overflow недостаточно.
U02 P1 Главная передаёт waterbody/fish/hours только в activity; feed получает лишь limit. После выбора водоёма снизу остаются чужие сигналы. Фильтровать оба блока согласованно либо явно подписать независимый общий feed. URL и сброс сохраняют понятную область действия.
U03 P1 Главная показывает первые 20 activity, records — 50, каталоги — 100 activity; навигации по остальным нет. Счётчик items.length выглядит общим числом. «Показано N из M», пагинация/следующая страница с сохранением фильтров; отдельно считать уникальные точки и комбинации точка+рыба.
U04 P1 Ошибки 422/429/5xx формы сводятся к «Проверьте поля»; нет ожидания/защиты от повторной отправки. Сохранение текста через sessionStorage не гарантировано при его недоступности; файл не восстанавливается. Разные полезные сообщения, Retry-After для 429, блокировка повторного submit, честная подсказка о файле, server fallback.
U05 P2 В 390×844 результаты начинаются около y=700, mobile меню скрывает последний пункт; при 0 точек зелёный «пульс» всё ещё изображает живую активность. Компактный первый экран, заметный доступ к скрытой навигации, отдельные empty/stale/error состояния, прямые действия «72 часа», «Сбросить», «Добавить улов».
U06 P2 Карточки многократно повторяют качество/доверие; «Не рассчитана», «сырые данные», «сюжеты» описывают реализацию. Текстовые badges часто 911 px. Иерархия рыба → точка → приманка → возраст → источник; понятные статусы без выдуманных процентов, крупнее значимые подписи.
U07 P2 Предыдущая визуальная приёмка недостаточна: главная была пустой. Матрица empty/1/many/long names/error; desktop/mobile, клавиатура, 200% zoom, раскрытые фильтры и форма; проверить также каталоги, detail и admin. Отдельно повторить axe, не приравнивая его к UX-аудиту.

Визуальная идентичность

Сильная основа: тёмный хвойный фон, лаймовый акцент, спокойная бумажная подложка, контрастная антиква, озеро, крючок, поплавок, рябь и координатный радар. Это стоит сохранить. Следующие пункты — дизайнерские предложения, не программные дефекты.

ID Приоритет Предложение Проверяемый результат
V01 P2 Утвердить RF4 Spotter как основное имя, «Ни хвоста, ни чешуи» как слоган либо явно выбрать обратную иерархию. Одинаковое узнаваемое имя в шапке, favicon/manifest, title, OG и footer.
V02 P2 Свести размеры текста, отступы, радиусы, тени, focus и семантические цвета в небольшой набор CSS-токенов. Компонентные состояния document/demo; источник и качество различаются текстом/формой, не только цветом. Разделить монолитный минифицированный global.css по компонентам.
V03 P2 Сделать паспорт точки компактным «полевым журналом»: координатная метка с копированием, линия времени, SVG рыбы, легенда шкалы. Один основной визуальный акцент на карточку; индекс не выглядит вероятностью поимки, радар не выдаётся за настоящую карту.
V04 P2 Проверить маленькие знаки 16/32 px, sharpness app icons и maskable safe area; точность OG alt. Чёткие отдельные SVG/PNG варианты; OG обещает реальный продукт и имеет осмысленную подпись изображения.
V05 P3 Отдельные обложки водоёмов и тематические OG для рыбы/точки. Только после согласования прав и измерения веса; не препятствуют чтению карточек и загрузке.

SEO и производительность

SSR, русский lang, canonical, OG/Twitter, JSON-LD, sitemap, slug URL и 301 со старых UUID уже есть. Недостающие метатеги не являются главным препятствием: сейчас важнее корректные ответы, полнота каталога и полезный постоянный контент.

ID Приоритет Подтверждение/риск Критерий исправления
S01 P1 index/records/report/status при сбое API отображают ошибку с HTTP 200; в detail возможен частично заполненный spot и noindex={!spot}. Единая таблица 200/404/422/503, no-store/Retry-After и политика индексации ошибок. Не выдавать пустую аварийную страницу за нормальный результат.
S02 P1 Sitemap собирает полный справочник, но detail ищет slug только в первых 500 элементах; формы загружают первые 200. При росте появятся sitemap URL с ложным 404. Прямые slug endpoints и пагинация каталогов; все sitemap URL разрешаются независимо от позиции в справочнике.
S03 P2 canonical убирает все query; structured data содержит жёсткий домен, PUBLIC_SITE_URL не передаётся в Docker build. Отдельная политика trailing slash/пагинации не оформлена. Единый origin, политика вариантов URL и страниц пагинации; внутренние ссылки используют конечные canonical URL (в sidebar ещё UUID redirect).
S04 P2 Постоянные страницы рыба+водоём живут в sitemap, но контент ограничен 72 часами и быстро превращается в шаблон «данных нет». Полезный подтверждённый справочный контент, дата обновления/источники, архив последних известных наблюдений с явной давностью, видимые breadcrumbs. Решение об индексации пустых комбинаций.
S05 P2 Sitemap lastGood хранится в памяти без максимального срока fallback; >49000 путей вызывает ошибку вместо index. Документированный срок fallback, sitemap index при необходимости, корректный lastmod только по реальным изменениям.
S06 P2 Hero уже WebP ~71 КБ, но не включён в Caddy matcher brandAssets; старый LCP 9,3 с не является текущим измерением. Проверить фактический Cache-Control hero; свежий mobile/desktop performance baseline после P0/U01, затем production Core Web Vitals. Не обещать ускорение в секундах без измерения.
S07 P2 robots содержит обычные правила и не закрывает весь staging; поисковые сервисы/превью ещё не проверены на рабочем домене. Закрытие тестового окружения, проверка Google Search Console и Яндекс Вебмастера после DNS/TLS, sitemap/JSON-LD/OG validation и наблюдение за индексацией.

Политика HTTP основана на Google: HTTP status codes, а согласование URL — на Google: canonical URLs. Наличие schema/сайткарты само по себе не обещает индексацию или позиции.

Порядок реализации

  1. T01T06: production маршруты, auth, сеть, клиентский лимит и реальная приёмка через Caddy.
  2. D01D08, T07: лимиты/публикация, наполнение, честное время и оценка конкретной точки.
  3. U01U04, S01–S02: основные сценарии, пагинация, ошибки и доступность индексируемых страниц.
  4. V01V04, U05U07, S03S06: визуальная система, понятность и поисковое качество.
  5. T08T10, D09D10 и внешний launch checklist; S07 на рабочем домене. V05 после пилота.

Тестировать по риску: адресные unit/contract проверки, один общий proxy/Compose-прогон для инфраструктурного пакета, браузер для визуального пакета. Документация и небольшие стилевые правки не требуют пересборки всех контейнеров. Статус выполнения ведётся в ROADMAP по ID этого отчёта.