fix: add error logging to empty catch blocks
This commit is contained in:
@@ -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` (проверка типов)
|
||||
Reference in New Issue
Block a user