Контекст: Карта архитектуры и технического долга проекта PhotoPlaces. Используется для планирования рефакторинга и оценки жизнеспособности.
Статус: Заметка актуализирована 2026-06-24 после code review. Предыдущая версия содержала устаревшие P0, которые уже исправлены в коде.
Проект — fullstack приложение (Next.js + Go) для карты мест фотосъёмок, аренды студий и найма фотографов. Использует PostgreSQL + PostGIS, Redis, MinIO. Развертывание через Docker Compose.
docker-compose.yml (postgres, redis, minio, backend, frontend)deploy/docker-compose.prod.yml + Caddy reverse proxyCI/CD: GitHub Actions (dependabot.yml)
graph TB
FB[Frontend Next.js 14] --> API[Go API :8080]
API --> PG[PostgreSQL + PostGIS]
API --> RD[Redis]
API --> S3[MinIO / S3]
FB --> FBStatic[Next.js Static]
CADDY[Caddy] --> FB
CADDY --> API
CADDY --> S3
| Проблема | Статус | Где |
|---|---|---|
| Refresh tokens не хранятся в БД | ✅ Исправлено | services/auth.go — SHA-256, ротация, DeleteIfExists |
| Rate limiter только in-memory | ✅ Исправлено | main.go — Redis + fallback, fail-open/closed |
| Нет валидации входных данных | ✅ Исправлено | validator/validator.go — go-playground/validator |
| Нет тестов | ✅ Частично | auth_test.go, places_test.go, place_repo_test.go |
| Package-level mutable state | ✅ Исправлено | handlers/errors.go — atomic.Bool вместо var AppEnv string |
| Координаты [lng,lat]/[lat,lng] | ✅ Исправлено | lib/map.ts — консистентно (lat, lng) везде |
| N+1 в GetByID | ✅ Исправлено | repository/places.go:63-109 — один LEFT JOIN + json_agg |
| Подавление ошибок PlaceForm | ✅ Исправлено | components/PlaceForm.tsx:50 — ошибки через setError |
| Мало линтеров | ✅ Исправлено | .golangci.yml — добавлены gosec, bodyclose, gocritic, revive |
| Проблема | Файл | Описание |
|---|---|---|
| TOCTOU race при регистрации | services/auth.go:85-111 |
GetByEmail → Create без транзакции |
| Mutex без defer | middleware/ratelimit.go:61 |
rl.mu.Lock() без defer Unlock() — deadlock при панике |
| Эскалация привилегий | handlers/users.go:166-186 |
Модератор может повысить любого до superadmin |
| Проблема | Файл | Описание |
|---|---|---|
| Retry loop на /auth/logout | lib/api.ts:71 |
/auth/logout не в noRefreshPaths — logout с истёкшим токеном циклит refresh |
| Лишние запросы при пагинации | services/places.go:148-172 |
GetTagsBatch/GetFeaturesBatch вызываются до обрезки limit+1 |
| Хрупкий парсинг bounds | handlers/places.go:76-80 |
fmt.Sscanf — silent fail при ошибке |
| Отсутствует статус по умолчанию у user | repository/users.go:22-29 |
INSERT без status — новый пользователь имеет пустой статус |
| Нет проверки статуса place при бронировании | repository/bookings.go:25-63 |
Можно забронировать место в draft/pending_moderation |
| RoleMiddleware: unsafe type assertion | middleware/auth.go:54 |
ctx.Value(UserRoleKey).(string) запаникует при не-строке |
| Проблема | Файл | Описание |
|---|---|---|
| ErrorBoundary молчит в production | components/ErrorBoundary.tsx:26-29 |
if (NODE_ENV === 'production') return — ошибки не логируются |
| Полная перерисовка маркеров | components/MapView.tsx:98-115 |
Все маркеры удаляются и создаются заново при каждом изменении places |
| node_modules volume в Go backend | docker-compose.yml:88 |
backend-modules:/app/node_modules — не имеет смысла для Go |
CSP с unsafe-inline |
deploy/Caddyfile:39 |
script-src и style-src с unsafe-inline — ослабляет XSS-защиту |
| N+1 в services/GetByID | handlers/services.go:80-85 |
Отдельный запрос GetTags после GetByID |
| DeleteTag: парсинг body | handlers/tags.go:79-84 |
DELETE читает JSON body, затем fallback на query |
| broadcastVisitors: двойной lock | handlers/websocket.go:91-127 |
Состояние может измениться между сбором и рассылкой |
| logout на фронте — подавление ошибок | hooks/useAuth.tsx:65 |
catch {} скрывает ошибки сети |
| fetchPlaces — подавление ошибок | components/MapView.tsx:37-39 |
Ошибка загрузки мест не отображается пользователю |
| next.config.js: нет S3-прокси для image optimization | next.config.js:5-8 |
Изображения через api.{DOMAIN}/s3/ не оптимизируются |
#architecture #backend #frontend #security #technical-debt #moc