390 lines
15 KiB
Markdown
390 lines
15 KiB
Markdown
# Полный аудит проекта RF4 Spotter — 10 сентября 2026
|
||
|
||
База: `13e04e6`. Проверены: архитектура, backend, frontend, security, performance, testing, deployment, documentation.
|
||
|
||
---
|
||
|
||
## 1. Архитектура и высокоуровневый обзор
|
||
|
||
### Стек
|
||
| Слой | Технология | Статус |
|
||
|------|-----------|--------|
|
||
| **Proxy** | Caddy 2.10 | Production-ready, TLS завершение, Basic Auth на admin |
|
||
| **Frontend** | Astro 7 SSR (Node) | Сборка 0 errors, адаптивный дизайн |
|
||
| **Backend** | FastAPI + SQLAlchemy 2 + PostgreSQL 17 | 130 тестов, 1 skipped |
|
||
| **Хранилище** | PostgreSQL 17 + MinIO/S3 | Volumes, backup/restore |
|
||
| **CI** | Gitea Actions | Python, Astro, Compose E2E, pip-audit |
|
||
| **Scheduler** | Official import + Community scheduler | Опциональный профиль |
|
||
|
||
### Оценка архитектуры: ✅ Хорошо (8/10)
|
||
- Чёткое разделение: Caddy → Astro SSR → FastAPI → Postgres/MinIO
|
||
- Нет точки отказа в виде единого контейнера
|
||
- Profiles в docker-compose для scheduler/importer
|
||
- Production compose отдельно от dev
|
||
|
||
---
|
||
|
||
## 2. Backend (FastAPI) — детальный разбор
|
||
|
||
### 2.1 `main.py` (~1100 строк) — 🔴 КРИТИЧЕСКАЯ ПРОБЛЕМА
|
||
|
||
**Проблемы:**
|
||
- Все endpoints в одном файле — нарушение SRP
|
||
- `create_catch_report` — 50+ строк бизнес-логики inline
|
||
- `_check_rate_limit` и `_is_trusted_proxy` — утилиты должны быть в отдельном модуле
|
||
- `_admin`, `_spot_or_404`, `_report_source` — общие функции смешаны с endpoints
|
||
- Нет APIRouter — всё на `app.get/post`
|
||
|
||
**Рекомендация:** Разделить на `routes/` с `APIRouter`:
|
||
```
|
||
apps/api/app/routes/
|
||
├── activity.py # /api/v1/activity, /spots/*
|
||
├── submissions.py # catch-reports, screenshots
|
||
├── admin.py # /admin/*
|
||
├── community.py # observations, source-status
|
||
├── records.py # /records, /imports
|
||
└── catalog.py # fishes, waterbodies, baits
|
||
```
|
||
|
||
### 2.2 `activity.py` — ⚠️ Есть риски при scale
|
||
|
||
**Плюсы:**
|
||
- Детерминированная формула, покрыта тестами
|
||
- Cap confidence для 1-2 игроков (A07)
|
||
- Exponential decay freshness (18h half-life)
|
||
|
||
**Риски:**
|
||
- Нет индекса на `(spot_id, fish_id)` — `groups` агрегирует в памяти
|
||
- При 10k+ approved reports за 72h — полный load + sort в памяти
|
||
- `total = len(rows)` — нет `COUNT` запроса, полная выборка для pagination
|
||
|
||
**Рекомендация:**
|
||
1. Добавить composite index: `ix_catch_report_spot_fish_approved`
|
||
2. Для activity endpoint — materialized view или pre-aggregation table
|
||
3. Для total — отдельный `COUNT` query вместо `len(rows)`
|
||
|
||
### 2.3 `models.py` — ✅ Хорошо структурирован
|
||
|
||
**Плюсы:**
|
||
- Чёткие enum для SourceType, ModerationStatus, ImportStatus
|
||
- Unique constraints на slug, external_id
|
||
- JSON поля для raw_payload, changes, provenance
|
||
- Foreign keys с relationships
|
||
|
||
**Проблемы:**
|
||
- `source_external_id` unique=True — конфликт при повторном импорте разных sources с одинаковым ID
|
||
- `SubmissionAttempt` — нет cleanup cron, растёт бесконечно (хотя есть cleanup в `_check_rate_limit`)
|
||
- `ImportRecordEvent` — нет индекса на `(catch_report_id, created_at)` для history query
|
||
|
||
### 2.4 `importer.py` — ✅ Хорошо, но есть edge cases
|
||
|
||
**Плюсы:**
|
||
- Advisory lock для cross-process mutex
|
||
- ETag/Last-Modified conditional requests
|
||
- Idempotent по external_id SHA-256
|
||
- A12: meaningful changes/provenance в ImportRecordEvent
|
||
|
||
**Риски:**
|
||
- `html: str | None = None` — тестовый параметр в production signature
|
||
- `_unique_slug` — N+1 query loop при коллизиях
|
||
- Нет retry policy для HTTP 5xx с exponential backoff
|
||
|
||
### 2.5 `retention.py` — ✅ Хорошо
|
||
|
||
**Плюсы:**
|
||
- Dry-run mode по умолчанию
|
||
- Configurable policy через RetentionPolicy
|
||
- Handles screenshots, payloads, moderation events
|
||
- Автоматический rejection pending reports по TTL
|
||
|
||
**Проблемы:**
|
||
- `delete_object` callback — должен быть injected, не passed per-call
|
||
- Нет bulk delete — loop по reports
|
||
|
||
### 2.6 `scheduler.py` vs `community_scheduler.py` — ⚠️ Дублирование
|
||
|
||
Оба используют pattern:
|
||
```python
|
||
while True:
|
||
try:
|
||
run_due_import()
|
||
except Exception:
|
||
logger.exception(...)
|
||
time.sleep(interval)
|
||
```
|
||
|
||
**Рекомендация:** Общий base class или use APScheduler/celery-beat
|
||
|
||
---
|
||
|
||
## 3. Frontend (Astro) — детальный разбор
|
||
|
||
### 3.1 `index.astro` — ⚠️ Сложная логика в template
|
||
|
||
**Проблемы:**
|
||
- 50+ строк бизнес-логики в `.astro` файле
|
||
- Нет error boundary — catch all делает 503
|
||
- `offset + items.length < totalItems` — работает, но fragile при partial last page
|
||
- Нет optimistic UI / loading state
|
||
|
||
**Рекомендация:**
|
||
1. Вынести fetch logic в `src/lib/fetchers.ts`
|
||
2. Добавить `isLoading` state и skeleton UI
|
||
3. Error boundary per-section (filters, activity, signals)
|
||
|
||
### 3.2 `Layout.astro` — ✅ Хорошо
|
||
|
||
**Плюсы:**
|
||
- A08: errorPage prop для skip structuredData
|
||
- Canonical, OG, Twitter Card, JSON-LD
|
||
- robots/noindex для admin/error pages
|
||
- Skip link для accessibility
|
||
|
||
**Минусы:**
|
||
- 12 CSS imports — можно aggregate в `global.css`
|
||
- `replaceAll("<", "\\u003c")` — hack для JSON escaping
|
||
|
||
### 3.3 `global.css` (~1200 строк) — ⚠️ Все стили вместе
|
||
|
||
**Проблемы:**
|
||
- Все компоненты в одном файле — сложно navigate
|
||
- Нет CSS modules или scoped styles
|
||
|
||
**Рекомендация:**
|
||
- Разделить на `components/`, `pages/`, `utilities/`
|
||
- Astro scoped styles для компонентов
|
||
|
||
---
|
||
|
||
## 4. Security — ✅ В целом хорошо, есть улучшения
|
||
|
||
### ✅ Реализовано
|
||
| Мера | Статус |
|
||
|------|--------|
|
||
| Rate limiting (PostgreSQL advisory lock) | ✅ |
|
||
| Idempotency-Key header | ✅ (A05) |
|
||
| HMAC client_hash вместо IP | ✅ |
|
||
| Screenshot validation (MIME, size, EXIF removal) | ✅ |
|
||
| Admin Bearer token + Caddy Basic Auth | ✅ |
|
||
| Security headers (HSTS, X-Frame-Deny, etc.) | ✅ |
|
||
| Production settings validation | ✅ |
|
||
| pip-audit в CI | ✅ |
|
||
|
||
### ⚠️ Улучшения
|
||
| Проблема | Приоритет |
|
||
|----------|----------|
|
||
| **CORS origins** — dev defaults `localhost:4321`, production требует HTTPS | Medium |
|
||
| **ADMIN_TOKEN** — default `change-me-in-production` в compose.yaml | High (docs) |
|
||
| **X-Content-Type-Options** — только на admin/catch-reports, не на всех | Low |
|
||
| **Content-Security-Policy** — отсутствует | Medium |
|
||
| **Rate limit** — 5 requests per 10 min, нет per-endpoint limits | Low |
|
||
| **Screenshot upload** — нет CSRF protection (POST без token) | Medium |
|
||
|
||
---
|
||
|
||
## 5. Performance — ⚠️ Есть узкие места
|
||
|
||
### 5.1 Database
|
||
|
||
**Проблемы:**
|
||
- `activity` endpoint: full table scan + in-memory sort
|
||
- `spot_detail`: `count_since` — Python loop по reports
|
||
- `public_spot_pages`: 3-way JOIN + DISTINCT + offset pagination
|
||
|
||
**Рекомендации — SQL индексы:**
|
||
```sql
|
||
-- Добавить индексы
|
||
CREATE INDEX ix_catch_report_spot_fish_approved
|
||
ON catch_report(spot_id, fish_id, moderation_status, reported_at);
|
||
|
||
CREATE INDEX ix_catch_report_waterbody_approved_reported
|
||
ON catch_report(waterbody_id, moderation_status, reported_at);
|
||
|
||
CREATE INDEX ix_catch_report_official_records
|
||
ON catch_report(source_type, caught_at, weight_g)
|
||
WHERE source_type = 'official_record';
|
||
```
|
||
|
||
### 5.2 Caching
|
||
|
||
**Текущее:**
|
||
- In-memory cache: 20s TTL, 128 keys, process-local
|
||
- `public_cache.invalidate()` на moderation/publish
|
||
|
||
**Проблемы:**
|
||
- Multi-process: каждый API worker имеет свой cache
|
||
- Нет cache warming на restart
|
||
- Community observations не кэшируются
|
||
|
||
**Рекомендация:** Redis или shared memory (SHM) для multi-process
|
||
|
||
### 5.3 Frontend
|
||
|
||
**Плюсы:**
|
||
- Astro SSR — no client JS for initial render
|
||
- `fetchpriority="high"` для hero image
|
||
- Reduced motion support
|
||
|
||
**Улучшения:**
|
||
- Нет lazy loading для нижеfold images
|
||
- Нет prefetch для `/spots/[id]`
|
||
- SignalFeed — нет intersection observer для "load more"
|
||
|
||
---
|
||
|
||
## 6. Testing — ✅ 130 passed, 1 skipped
|
||
|
||
### ✅ Хорошее покрытие
|
||
- API endpoints (test_api.py)
|
||
- Import idempotency (test_importer.py)
|
||
- Community CLI state/validation (test_community_cli.py)
|
||
- Readiness aggregation (test_readiness.py)
|
||
- Rate limit with Docker chain (test_rate_limit.py)
|
||
|
||
### ⚠️ Пропущенные сценарии
|
||
| Тест | Приоритет |
|
||
|------|----------|
|
||
| **Retention cleanup** — нет интеграционного теста | High |
|
||
| **Screenshot validation** — edge cases (PNG vs JPEG magic bytes) | Medium |
|
||
| **Community scheduler retry backoff** — 30min → 24h | Medium |
|
||
| **Activity calculation** — edge cases (0 players, all same player) | Low |
|
||
| **Bootstrap E2E** — test-production-bootstrap.sh не в CI | Medium |
|
||
| **Astro accessibility** — audit:axe есть, но нет automated checks | Low |
|
||
|
||
---
|
||
|
||
## 7. Deployment & Operations
|
||
|
||
### ✅ Хорошее
|
||
- Compose production с healthchecks
|
||
- Backup/restore scripts
|
||
- Monitoring docs (disk, TLS, readiness)
|
||
- Data retention policy
|
||
- Preflight checks before deploy
|
||
|
||
### ⚠️ Улучшения
|
||
| Проблема | Решение |
|
||
|----------|---------|
|
||
| **Нет blue-green/canary** | Добавить rolling update в deploy script |
|
||
| **Database migration rollback** | Нет test-rollback в bootstrap |
|
||
| **Log rotation** — Docker json-file, 10MB × 5 | OK, но нет centralized logging |
|
||
| **No distributed tracing** | Добавить OpenTelemetry для request_id propagation |
|
||
| **No metrics export** | Нет Prometheus metrics (request count, latency, error rate) |
|
||
|
||
---
|
||
|
||
## 8. Documentation
|
||
|
||
### ✅ Хорошее
|
||
- README.md — comprehensive (150+ строк)
|
||
- docs/ — recovery plans, audit reports, roadmap
|
||
- deploy/README.md — production steps
|
||
- data-policy.md, retention, monitoring
|
||
|
||
### ⚠️ Улучшения
|
||
| Проблема | Решение |
|
||
|----------|---------|
|
||
| **API documentation** — FastAPI auto-docs, но нет OpenAPI spec file | Добавить `openapi.json` в repo |
|
||
| **Data model ER diagram** | Добавить визуальную схему |
|
||
| **Architecture decision records (ADR)** | Для key decisions (Astro vs Next, in-memory cache, etc.) |
|
||
| **Runbook for incidents** | Что делать при Postgres full, MinIO down, etc. |
|
||
|
||
---
|
||
|
||
## 9. Код-стиль и maintainability
|
||
|
||
### ✅ Хорошее
|
||
- Type hints everywhere
|
||
- `from __future__ import annotations`
|
||
- Pydantic v2 models
|
||
- Alembic migrations
|
||
|
||
### ⚠️ Улучшения
|
||
| Проблема | Пример |
|
||
|----------|--------|
|
||
| **Long lines** | `create_catch_report` — 70+ char lines |
|
||
| **Magic numbers** | `55 * min(1, weighted / 12)` — вынести константы |
|
||
| **Inline SQL** | `text("SELECT pg_try_advisory_lock(:key)")` — вынести в constants |
|
||
| **Duplicate URL patterns** | `"/api/v1/admin/"` repeated in middleware and endpoints |
|
||
|
||
---
|
||
|
||
## 10. Приоритизированный список улучшений
|
||
|
||
### 🔴 High Priority (блокирующие/рисковые)
|
||
|
||
1. **Разделить main.py на routers** — SRP, testability, reviewability
|
||
2. **Добавить database indexes** — `spot_id + fish_id + moderation_status`, `source_type + official_records`
|
||
3. **Retention integration test** — verify cleanup works end-to-end
|
||
4. **Screenshot CSRF protection** — form submission без CSRF token
|
||
5. **Bootstrap E2E в CI** — `test-production-bootstrap.sh` должен run на push
|
||
|
||
### 🟡 Medium Priority (улучшения)
|
||
|
||
6. **Shared cache (Redis)** — вместо in-memory для multi-process
|
||
7. **Content-Security-Policy header** — добавить base-uri, script-src
|
||
8. **Error boundaries в Astro** — per-section error handling
|
||
9. **Materialized view для activity** — pre-aggregation вместо in-memory sort
|
||
10. **OpenAPI spec export** — `openapi.json` в repo + swagger UI
|
||
11. **Metrics export (Prometheus)** — request latency, error rate, queue depth
|
||
12. **Screenshot validation test** — magic bytes, MIME mismatch
|
||
|
||
### 🟢 Low Priority (косметика/технический долг)
|
||
|
||
13. **CSS modularization** — split global.css into components
|
||
14. **Extract constants** — 55, 25, 20, 12, 6, 3 → named constants
|
||
15. **Scheduler base class** — DRY для official + community scheduler
|
||
16. **Prefetch links** — `/spots/[id]` prefetch на hover
|
||
17. **ADR documentation** — record architecture decisions
|
||
18. **Incident runbook** — playbooks для common failures
|
||
|
||
---
|
||
|
||
## Итоговая оценка
|
||
|
||
| Категория | Оценка | Комментарий |
|
||
|-----------|--------|-------------|
|
||
| **Архитектура** | 8/10 | Чистое разделение, но main.py — god object |
|
||
| **Безопасность** | 7.5/10 | Хорошие основы, нужно CSP + CSRF |
|
||
| **Производительность** | 6.5/10 | Indexes + caching critical для scale |
|
||
| **Тестирование** | 8/10 | 130 tests, но пропущены retention + bootstrap |
|
||
| **Deploy/Ops** | 7.5/10 | Good compose, no metrics/tracing |
|
||
| **Документация** | 8.5/10 | Comprehensive README, missing runbook |
|
||
| **Code Quality** | 7.5/10 | Type hints, но long files + magic numbers |
|
||
|
||
**Общий балл: 7.6/10** — крепкий проект с хорошими основами, требует refactor main.py и database indexes для production scale.
|
||
|
||
---
|
||
|
||
## История ревизий
|
||
|
||
| Дата | Автор | Изменения |
|
||
|------|-------|----------|
|
||
| 2026-09-10 | AI | Полный аудит: архитектура, backend, frontend, security, performance, testing, deploy, docs |
|
||
| 2026-09-08 | (предыдущий) | Базовый аудит UI/UX, T01/T02 fixes |
|
||
|
||
---
|
||
|
||
## Что делать дальше (рекомендации)
|
||
|
||
### Немедленно (до продакшена)
|
||
1. Разделить main.py на routers (минимум 3 файла)
|
||
2. Создать и применить SQL индексы
|
||
3. Добавить retention integration test
|
||
4. Добавить CSRF token для screenshot upload
|
||
5. Добавить `test-production-bootstrap.sh` в CI workflow
|
||
|
||
### В течение спринта
|
||
6. Внедрить Redis для shared caching
|
||
7. Добавить CSP headers
|
||
8. Вынести fetch logic из index.astro в fetchers.ts
|
||
9. Добавить OpenAPI spec export
|
||
10. Написать incident runbook
|
||
|
||
### По возможности
|
||
11. Materialized view для activity
|
||
12. Prometheus metrics
|
||
13. CSS modularization
|
||
14. ADR documentation
|
||
15. Scheduler base class
|