96 lines
3.8 KiB
Markdown
96 lines
3.8 KiB
Markdown
# Spec: Image Processing Refactor
|
|
|
|
## Context
|
|
|
|
Current image handling uses on-demand resize via `/uploads-resized/` route. Admin uploads save originals as-is (jpg/png/webp), and resize happens on first request. User uploads (reviews, 2MB limit) also use on-demand resize.
|
|
|
|
## Goals
|
|
|
|
1. **User images (reviews, ≤2MB):** Improve size error messages to be user-friendly
|
|
2. **Admin images (products, ≤20MB):** Eager processing at upload time
|
|
- Generate all resize widths (320, 640, 1024, 1600) in AVIF + WebP
|
|
- Convert original to WebP (delete source file)
|
|
- Full-screen viewer shows original in WebP (no width limit)
|
|
- Thumbnails use resized versions from cache
|
|
|
|
## Architecture
|
|
|
|
### Server Changes
|
|
|
|
#### 1. `server/src/lib/upload-images.js`
|
|
- Add `eager` parameter to `persistMultipartImages`
|
|
- When `eager: true`, after saving each file:
|
|
1. Call `generateAllSizes(uuid, subdir, fullPath)` — generates all sizes from original
|
|
2. Call `convertOriginalToWebp(uuid, subdir)` — converts original to WebP, deletes source
|
|
3. Update URL to use `.webp` extension (replace original extension)
|
|
|
|
#### 2. `server/src/lib/image-resize.js`
|
|
- Add `generateAllSizes(uuid, subdir, originalPath)`:
|
|
- For each width in [320, 640, 1024, 1600]:
|
|
- Generate AVIF and WebP in `.cache/<subdir>/`
|
|
- Uses original file path (before conversion to WebP)
|
|
- Add `convertOriginalToWebp(uuid, subdir)`:
|
|
- Find original file (jpg/png)
|
|
- Convert to WebP (quality 80) at same location with `.webp` extension
|
|
- Delete original jpg/png file
|
|
- Return new `.webp` path
|
|
|
|
#### 3. `server/src/routes/api/admin-products.js`
|
|
- Pass `eager: true` to `persistMultipartImages`
|
|
|
|
#### 4. `server/src/routes/api/public-reviews.js`
|
|
- Improve error message for file too large (413)
|
|
|
|
### Client Changes
|
|
|
|
#### 1. `client/src/entities/product/api/product-api.ts`
|
|
- Add pre-upload size check for review images
|
|
- Clear error message: "Файл «<name>» слишком большой (максимум 2 МБ)"
|
|
|
|
#### 2. `client/src/shared/ui/OptimizedImage.tsx`
|
|
- Update `buildSrcSet` to use cached AVIF/WebP directly
|
|
- Full-screen viewer: use original `.webp` URL (no `?w=`)
|
|
- Remove fallback to original format for upload URLs
|
|
|
|
#### 3. `client/src/features/product-review/ui/ReviewDialog.tsx`
|
|
- Show user-friendly error message for oversized files
|
|
|
|
## Data Flow
|
|
|
|
### Admin Upload (Eager)
|
|
1. Client sends FormData to `POST /api/admin/uploads`
|
|
2. Server saves original (e.g., `uuid.jpg`)
|
|
3. Server generates all sizes in `.cache/` from original
|
|
4. Server converts original to WebP (`uuid.webp`), deletes `uuid.jpg`
|
|
5. Returns URLs with `.webp` extension (e.g., `/uploads/<uuid>.webp`)
|
|
6. Client displays using OptimizedImage with srcset from cache
|
|
|
|
### User Upload (Reviews)
|
|
1. Client validates file size ≤2MB before upload
|
|
2. Server validates and saves original
|
|
3. On-demand resize still works (existing flow)
|
|
4. Clear error messages at both client and server
|
|
|
|
## Error Handling
|
|
|
|
### User Upload Size Error
|
|
- **Client:** Pre-upload check with message "Файл «<name>» слишком большой (максимум 2 МБ)"
|
|
- **Server:** 413 with "Файл слишком большой (максимум 2 МБ)"
|
|
|
|
### Admin Upload Processing Error
|
|
- If sharp fails: return 500 with "Ошибка обработки изображения"
|
|
- If file not found after save: return 500 with "Внутренняя ошибка сервера"
|
|
|
|
## Testing
|
|
|
|
### Server Tests
|
|
- Test `generateAllSizes` creates all width+format combinations
|
|
- Test `convertOriginalToWebp` converts and deletes original
|
|
- Test `persistMultipartImages` with `eager: true`
|
|
- Test error messages for oversized files
|
|
|
|
### Client Tests
|
|
- Test pre-upload size validation for reviews
|
|
- Test OptimizedImage srcset generation for WebP originals
|
|
- Test error message display in ReviewDialog
|