fix: add error logging to empty catch blocks

This commit is contained in:
Kirill
2026-05-27 20:17:05 +05:00
parent 8f3bd7aa3b
commit f6414adf2f
26 changed files with 1590 additions and 34 deletions
@@ -0,0 +1,201 @@
# Refactoring Audit — Code Quality Improvements
Date: 2026-05-27
## Overview
Системный рефакторинг кодовой базы shop (craftshop monorepo) по 5 направлениям: toast-уведомления, пустые catch-блоки, серверное дублирование, клиентское дублирование, обработка ошибок API.
## Подпроект 1: Система toast-уведомлений
### Проблема
- Единственный `CartSnackbar` (Effector) только для "товар добавлен в корзину"
- Остальные операции без обратной связи — только inline `<Alert>` на страницах
- Нет централизованного показа ошибок от API
### Решение
**1.1 Effector-стор уведомлений** (`client/src/shared/model/notification.ts`)
```ts
type NotificationType = 'success' | 'error' | 'info' | 'warning'
interface Notification {
id: string
type: NotificationType
message: string
autoHideDuration?: number
}
// События
addNotification добавить (генерирует id + пушит в очередь)
dismissNotification закрыть по id
dismissAll закрыть все
// Стор
$notifications: Notification[] очередь, макс 3 видимых
```
**1.2 Компонент `NotificationStack`** (`client/src/shared/ui/NotificationStack/`)
- Использует MUI `Snackbar` + `Alert` (уже стилизованы через тему)
- Позиция: `bottom: 80px`
- `Slide` transition, auto-hide по таймеру
- Крестик для ручного закрытия
- Не более 3 одновременных уведомлений (остальные в очереди)
**1.3 Миграция CartSnackbar**
- `cartAdded` событие пишет в `addNotification({ type: 'info', message: 'Товар добавлен в корзину' })`
- Старый `CartSnackbar` удаляется (компонент, стор, тесты)
- `ToggleCartIcon` и `AddToCartButton` — убрать прямой вызов `cartAdded`, заменить на `addNotification`
**1.4 Интеграция с `useMutationWithToast`** (опционально, см. подпроект 5)
### Файлы
- Новые: `shared/model/notification.ts`, `shared/ui/NotificationStack/NotificationStack.tsx`, `shared/ui/NotificationStack/index.ts`
- Изменённые: `app/App.tsx` (+ NotificationStack), удаление `features/cart/model/cart-notifications.ts`, `features/cart/ui/CartSnackbar/`
- Тесты: `shared/model/notification.test.ts`, `shared/ui/NotificationStack/NotificationStack.test.tsx`
---
## Подпроект 2: Пустые catch-блоки
### Проблема
17 мест с `catch { /* ignore */ }` или пустым телом.
### Решение
Минимальное логирование с контекстом:
| Файл | Уровень | Сообщение |
|------|---------|-----------|
| `persist-token.ts` (3 места) | `console.warn` | `'[persist-token] Failed to ...'` |
| `theme-controller.tsx` (2 места) | `console.warn` | `'[theme] Failed to ...'` |
| `CookieConsentBanner.tsx` (2 места) | `console.warn` | `'[cookie-consent] Failed to ...'` |
| `SseProvider.tsx` | `console.warn` | `'[sse] Connection error:'` |
| `admin-gallery` (сервер) | `request.log.error` | Контекст операции |
| Остальные серверные | `request.log.error` | Контекст операции |
### Файлы
Только изменения в существующих файлах. Новых файлов нет.
---
## Подпроект 3: Сервер — дублирование в роутах
### Проблема
- ~25 мест с ручным try/catch-паттерном
- Дублирование валидации галерейных изображений (POST/PATCH admin/products)
- Дублирование `prisma.order.findFirst({ where: { id, userId } })` в 3 файлах
### Решение
**3.1 `asyncHandler`** (`server/src/lib/async-handler.js`)
```js
function asyncHandler(fn) {
return async (request, reply) => {
try {
return await fn(request, reply)
} catch (err) {
request.log.error(err)
const statusCode = err.statusCode || 500
const message = err.statusCode ? err.message : 'Internal server error'
return reply.code(statusCode).send({ error: message })
}
}
}
```
**3.2 `validateGalleryImages`** (`server/src/lib/validate-gallery-images.js`)
- Проверка существования `galleryImages` в БД
- Проверка наличия resized-версий
- Используется в POST и PATCH admin/products
**3.3 `findUserOrder`** (`server/src/lib/find-user-order.js`)
- `prisma.order.findFirst({ where: { id, userId }, ... })` с included relations
### Файлы
- Новые: `server/src/lib/async-handler.js`, `server/src/lib/validate-gallery-images.js`, `server/src/lib/find-user-order.js`
- Изменённые: ~10 server route files
---
## Подпроект 4: Клиент — дублирование
### Проблема
- 4 копии `useQuery({ queryKey: ['me', 'cart'], ... })`
- Дублирование `orderStatusLabelRu` и `ORDER_STATUS_DATA`
### Решение
**4.1 `useCartQuery`** (`client/src/entities/cart/lib/use-cart-query.ts`)
```ts
export function useCartQuery() {
const user = useAuthUser()
return useQuery({
queryKey: ['me', 'cart'],
queryFn: fetchMyCart,
enabled: Boolean(user),
})
}
```
Замена в 4 компонентах.
**4.2 Дубль статусов**
- `ORDER_STATUS_DATA` — единственный источник
- `orderStatusLabelRu` удалить, импорты переписать на `ORDER_STATUS_DATA`
### Файлы
- Новые: `entities/cart/lib/use-cart-query.ts`
- Изменённые: `AppHeader.tsx`, `CartPage.tsx`, `CheckoutPage.tsx`, `ToggleCartIcon.tsx`, удаление `order-status-labels.ts`
---
## Подпроект 5: Обработка ошибок API (клиент)
### Проблема
- Нет централизованного показа ошибок API
- `CheckoutPage` показывает сырое `(error as Error).message`
### Решение
**5.1 `useMutationWithToast`** (`client/src/shared/lib/use-mutation-with-toast.ts`)
```ts
function useMutationWithToast(options) {
return useMutation({
...options,
onSuccess: (data, ...rest) => {
if (options.successMessage) addNotification({ type: 'success', message: options.successMessage })
options.onSuccess?.(data, ...rest)
},
onError: (error, ...rest) => {
addNotification({ type: 'error', message: getApiErrorMessage(error) })
options.onError?.(error, ...rest)
},
})
}
```
**5.2 `getApiErrorMessage`** улучшение (`client/src/shared/lib`)
- Если сервер вернул `{ error: string }` — показать его
- Ошибка сети → "Нет соединения с сервером."
- 500 → "Произошла ошибка. Попробуйте позже."
- Остальное → стандартное сообщение
### Файлы
- Новые: `shared/lib/use-mutation-with-toast.ts`
- Изменённые: `shared/lib/get-api-error-message.ts`, `CheckoutPage.tsx` (исправить отображение ошибки)
---
## Порядок реализации
Подпроекты независимы и могут выполняться в любом порядке. Рекомендуемый порядок:
1. **Подпроект 2** (пустые catch) — быстрые и безопасные изменения, хороший разогрев
2. **Подпроект 1** (toast) — база для подпроекта 5, новая функциональность
3. **Подпроект 5** (useMutationWithToast) — строится поверх подпроекта 1
4. **Подпроект 3** (сервер) — standalone
5. **Подпроект 4** (клиент) — standalone
## Тестирование
- Каждый подпроект: `npm run lint` (или `npm run lint:fix`) + `npm test` для своей директории
- Финальная проверка: `cd client && npm run build` (проверка типов)