import crypto from 'node:crypto' import fs from 'node:fs' import path from 'node:path' export function safeImageExt(filename) { const ext = path.extname(String(filename || '')).toLowerCase() const allowed = new Set(['.png', '.jpg', '.jpeg', '.webp']) return allowed.has(ext) ? ext : null } export function uploadError(message, statusCode = 400) { const err = new Error(message) err.statusCode = statusCode return err } export async function persistMultipartImages(request, { maxFiles = 10, maxFileBytes, subdir = '', eager = false }) { if (!request.isMultipart()) { throw uploadError('Ожидается multipart/form-data') } const uploadsDir = path.join(process.cwd(), 'uploads') const targetDir = subdir ? path.join(uploadsDir, subdir) : uploadsDir await fs.promises.mkdir(targetDir, { recursive: true }) const urls = [] const parts = request.parts({ limits: { fileSize: maxFileBytes, files: maxFiles, }, }) for await (const part of parts) { if (!part.file) continue if (urls.length >= maxFiles) { throw uploadError(`Можно загрузить не более ${maxFiles} файл(ов)`) } const ext = safeImageExt(part.filename) if (!ext) { throw uploadError('Разрешены только файлы: png, jpg, jpeg, webp') } const uuid = crypto.randomUUID() const fileName = `${uuid}${ext}` const fullPath = path.join(targetDir, fileName) await fs.promises.writeFile(fullPath, await part.toBuffer()) let finalUrl = subdir ? `/uploads/${subdir}/${fileName}` : `/uploads/${fileName}` if (eager) { try { const { generateAllSizes, convertOriginalToWebp } = await import('./image-resize.js') await generateAllSizes(uuid, subdir, fullPath) finalUrl = await convertOriginalToWebp(uuid, subdir) } catch (error) { await fs.promises.unlink(fullPath).catch(() => {}) throw error } } urls.push(finalUrl) } if (urls.length === 0) { throw uploadError( 'Файлы не получены. Проверьте, что запрос multipart/form-data и поля — файлы изображений (png, jpg, webp).', ) } return urls } /** Сохранить один буфер изображения в uploads/, вернуть путь `/uploads/...`. */ export async function saveImageBufferToUploads(originalFilename, buffer, subdir = '') { const ext = safeImageExt(originalFilename) if (!ext) { throw uploadError('Разрешены только файлы: png, jpg, jpeg, webp') } const uploadsDir = path.join(process.cwd(), 'uploads') const targetDir = subdir ? path.join(uploadsDir, subdir) : uploadsDir await fs.promises.mkdir(targetDir, { recursive: true }) const fileName = `${crypto.randomUUID()}${ext}` const fullPath = path.join(targetDir, fileName) await fs.promises.writeFile(fullPath, buffer) return subdir ? `/uploads/${subdir}/${fileName}` : `/uploads/${fileName}` }