Files
shop-server/docs/superpowers/specs/2026-05-27-refactoring-audit-design.md
T
2026-05-27 20:56:08 +05:00

202 lines
8.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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` (проверка типов)