import fs from 'node:fs/promises' import path from 'node:path' import { prisma } from '../../lib/prisma.js' import { persistMultipartImages } from '../../lib/upload-images.js' import { formatFileTooLargeMessage, getProductImageMaxFileBytes, isMultipartFileTooLargeError, } from '../../lib/upload-limits.js' export async function registerAdminGalleryRoutes(fastify) { fastify.get('/api/admin/gallery', { preHandler: [fastify.verifyAdmin] }, async () => { const items = await prisma.galleryImage.findMany({ orderBy: { createdAt: 'desc' }, }) return { items } }) fastify.post('/api/admin/gallery/upload', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => { try { const urls = await persistMultipartImages(request, { maxFiles: 10, maxFileBytes: getProductImageMaxFileBytes(), subdir: '', eager: false, }) for (const url of urls) { await prisma.galleryImage.create({ data: { url, isResized: false }, }) } 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/gallery/:id/resize', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => { const { id } = request.params const row = await prisma.galleryImage.findUnique({ where: { id } }) if (!row) { return reply.code(404).send({ error: 'Изображение не найдено' }) } if (row.isResized) { return reply.code(409).send({ error: 'Изображение уже обработано' }) } const urlParts = row.url.replace(/^\//, '').split('/') const fileName = urlParts[urlParts.length - 1] const uuid = path.parse(fileName).name try { const { generateAllSizes, convertOriginalToWebp } = await import('../../lib/image-resize.js') const fullPath = path.join(process.cwd(), urlParts.slice(0, -1).join('/'), fileName) await generateAllSizes(uuid, '', fullPath) const newUrl = await convertOriginalToWebp(uuid, '') await prisma.galleryImage.update({ where: { id }, data: { url: newUrl, isResized: true }, }) return { url: newUrl } } catch (error) { request.log.error(error, 'Resize failed') return reply.code(500).send({ error: 'Ошибка обработки изображения' }) } }) fastify.delete('/api/admin/gallery/:id', { preHandler: [fastify.verifyAdmin] }, async (request, reply) => { const { id } = request.params const row = await prisma.galleryImage.findUnique({ where: { id } }) if (!row) { return reply.code(404).send({ error: 'Не найдено' }) } const usedInImages = await prisma.productImage.count({ where: { url: row.url } }) const usedAsLegacy = await prisma.product.count({ where: { imageUrl: row.url } }) if (usedInImages > 0 || usedAsLegacy > 0) { return reply.code(409).send({ error: 'Изображение используется в карточке товара' }) } const relative = row.url.replace(/^\//, '') const filePath = path.join(process.cwd(), relative) try { await fs.unlink(filePath) } catch (err) { if (err && typeof err === 'object' && 'code' in err && err.code !== 'ENOENT') { throw err } } await prisma.galleryImage.delete({ where: { id } }) return reply.code(204).send() }) }