|
|
@@ -43,14 +43,19 @@ graph TB
|
|
|
CADDY --> S3
|
|
|
```
|
|
|
|
|
|
-## Что было исправлено с момента первой версии Obsidian-заметок
|
|
|
+## Статус исправления проблем
|
|
|
|
|
|
-| Проблема (из старой заметки) | Статус | Где исправлено |
|
|
|
+| Проблема | Статус | Где |
|
|
|
|---|---|---|
|
|
|
| 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` |
|
|
|
|
|
|
## Актуальные проблемы (на 2026-06)
|
|
|
|
|
|
@@ -58,30 +63,35 @@ graph TB
|
|
|
|
|
|
| Проблема | Файл | Описание |
|
|
|
|---|---|---|
|
|
|
-| **Package-level mutable state** | `handlers/errors.go:13` | `var AppEnv string` — глобальная переменная, невозможно тестировать хендлеры |
|
|
|
-| **TOCTOU race при регистрации** | `services/auth.go:85-86` | `GetByEmail` → `Create` без транзакции, ошибка подавлена через `_` |
|
|
|
+| **TOCTOU race при регистрации** | `services/auth.go:85-111` | `GetByEmail` → `Create` без транзакции |
|
|
|
| **Mutex без defer** | `middleware/ratelimit.go:61` | `rl.mu.Lock()` без `defer Unlock()` — deadlock при панике |
|
|
|
-| **Координаты карты: [lng,lat] vs [lat,lng]** | `lib/map.ts` | Интерфейс `init()` принимает `[lng, lat]`, `setCenter()` — `[lat, lng]` |
|
|
|
+| **Эскалация привилегий** | `handlers/users.go:166-186` | Модератор может повысить любого до superadmin |
|
|
|
|
|
|
### P1 — Высокие
|
|
|
|
|
|
| Проблема | Файл | Описание |
|
|
|
|---|---|---|
|
|
|
-| N+1 запросы в GetByID | `services/places.go:112-136` | 3 отдельных запроса вместо JOIN |
|
|
|
-| Лишние запросы при пагинации | `services/places.go:148-168` | Tags/features подгружаются для отбрасываемого `limit+1` элемента |
|
|
|
-| Подавление ошибок API на фронте | `components/PlaceForm.tsx:42-43` | `.catch(() => {})` — пользователь не видит ошибок загрузки тегов |
|
|
|
-| Retry loop на /auth/logout | `lib/api.ts:71` | `!path.includes('/auth/')` не исключает `/auth/logout` |
|
|
|
-| Хрупкий парсинг bounds | `handlers/places.go:55-59` | `fmt.Sscanf` без проверки ошибки |
|
|
|
+| 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)` запаникует при не-строке |
|
|
|
|
|
|
### P2 — Умеренные
|
|
|
|
|
|
-| Проблема | Описание |
|
|
|
-|---|---|
|
|
|
-| ErrorBoundary молчит в production | `console.debug` в проде не вызывается — ошибки не логируются |
|
|
|
-| Полная перерисовка маркеров | Все маркеры удаляются и создаются заново при каждом изменении `places` |
|
|
|
-| node_modules volume в Go backend | `docker-compose.yml:88` — не имеет смысла |
|
|
|
-| CSP с `unsafe-inline` и `unsafe-eval` | Caddyfile — ослабляет XSS-защиту |
|
|
|
-| Мало линтеров в `.golangci.yml` | Нет `gosec`, `bodyclose`, `gocritic`, `revive` |
|
|
|
+| Проблема | Файл | Описание |
|
|
|
+|---|---|---|
|
|
|
+| 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/` не оптимизируются |
|
|
|
|
|
|
## Архитектурные решения (Decision Records)
|
|
|
|