From 5637bb7db9c20d5757372b50c01968fe04f951c5 Mon Sep 17 00:00:00 2001 From: Kirill Date: Sun, 17 May 2026 17:51:47 +0500 Subject: [PATCH] feat(server): remove old /admin/uploads, validate isResized on product endpoints --- server/src/index.js | 2 +- server/src/routes/api/admin-products.js | 72 +++++++++++++------------ 2 files changed, 39 insertions(+), 35 deletions(-) diff --git a/server/src/index.js b/server/src/index.js index 3fc5684..c5bf3be 100644 --- a/server/src/index.js +++ b/server/src/index.js @@ -42,7 +42,7 @@ await fastify.register(jwt, { await fastify.register(multipart, { limits: { files: 10, - /** Совпадает с лимитом одного файла для `POST /api/admin/uploads` (товары, галерея). */ + /** Совпадает с лимитом одного файла для `POST /api/admin/gallery/upload` (галерея). */ fileSize: getProductImageMaxFileBytes(), }, }) diff --git a/server/src/routes/api/admin-products.js b/server/src/routes/api/admin-products.js index 8492402..2294660 100644 --- a/server/src/routes/api/admin-products.js +++ b/server/src/routes/api/admin-products.js @@ -1,11 +1,4 @@ -import { upsertGalleryImagesByUrls } from '../../lib/gallery.js' import { prisma } from '../../lib/prisma.js' -import { - formatFileTooLargeMessage, - getProductImageMaxFileBytes, - isMultipartFileTooLargeError, -} from '../../lib/upload-limits.js' -import { persistMultipartImages } from '../../lib/upload-images.js' const CREATE_PRODUCT_SCHEMA = { body: { @@ -59,33 +52,6 @@ export async function registerAdminProductRoutes(fastify) { }, ) - fastify.post( - '/api/admin/uploads', - { preHandler: [fastify.verifyAdmin] }, - async (request, reply) => { - try { - const urls = await persistMultipartImages(request, { - maxFiles: 10, - maxFileBytes: getProductImageMaxFileBytes(), - eager: true, - }) - await upsertGalleryImagesByUrls(urls) - return { urls } - } catch (error) { - let message = error instanceof Error ? error.message : 'Не удалось загрузить файлы' - let statusCode = - error && typeof error === 'object' && 'statusCode' in error && Number.isInteger(error.statusCode) - ? Number(error.statusCode) - : 400 - if (isMultipartFileTooLargeError(error)) { - message = formatFileTooLargeMessage(getProductImageMaxFileBytes()) - statusCode = 413 - } - return reply.code(statusCode).send({ error: message }) - } - }, - ) - fastify.post( '/api/admin/products', { preHandler: [fastify.verifyAdmin], schema: CREATE_PRODUCT_SCHEMA }, @@ -122,6 +88,25 @@ export async function registerAdminProductRoutes(fastify) { return } + if (Array.isArray(body.imageUrls) && body.imageUrls.length > 0) { + const urls = body.imageUrls.map((u) => String(u || '').trim()).filter(Boolean) + if (urls.length > 0) { + const galleryImages = await prisma.galleryImage.findMany({ + where: { url: { in: urls } }, + select: { url: true, isResized: true }, + }) + const galleryMap = new Map(galleryImages.map((g) => [g.url, g])) + const notFound = urls.filter((u) => !galleryMap.has(u)) + const notResized = urls.filter((u) => galleryMap.get(u) && !galleryMap.get(u).isResized) + if (notFound.length > 0) { + return reply.code(400).send({ error: 'Некоторые изображения не найдены в галерее' }) + } + if (notResized.length > 0) { + return reply.code(400).send({ error: 'Изображения должны быть обработаны (resize) перед прикреплением к товару' }) + } + } + } + const n = Number(body.quantity) if (!Number.isInteger(n) || n < 0 || n > 10) { reply.code(400).send({ error: 'Количество — целое число от 0 до 10' }) @@ -228,6 +213,25 @@ export async function registerAdminProductRoutes(fastify) { data.categoryId = cid } + if (body.imageUrls !== undefined && Array.isArray(body.imageUrls)) { + const urls = body.imageUrls.map((u) => String(u || '').trim()).filter(Boolean) + if (urls.length > 0) { + const galleryImages = await prisma.galleryImage.findMany({ + where: { url: { in: urls } }, + select: { url: true, isResized: true }, + }) + const galleryMap = new Map(galleryImages.map((g) => [g.url, g])) + const notFound = urls.filter((u) => !galleryMap.has(u)) + const notResized = urls.filter((u) => galleryMap.get(u) && !galleryMap.get(u).isResized) + if (notFound.length > 0) { + return reply.code(400).send({ error: 'Некоторые изображения не найдены в галерее' }) + } + if (notResized.length > 0) { + return reply.code(400).send({ error: 'Изображения должны быть обработаны (resize) перед прикреплением к товару' }) + } + } + } + const imagesUpdate = body.imageUrls !== undefined ? {