This commit is contained in:
@kirill.komarov
2026-05-10 17:38:04 +05:00
parent df4435dd67
commit 20096c1eec
12 changed files with 361 additions and 1 deletions
@@ -0,0 +1,9 @@
-- CreateTable
CREATE TABLE "GalleryImage" (
"id" TEXT NOT NULL PRIMARY KEY,
"url" TEXT NOT NULL,
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- CreateIndex
CREATE UNIQUE INDEX "GalleryImage_url_key" ON "GalleryImage"("url");
+7
View File
@@ -55,6 +55,13 @@ model ProductImage {
@@index([productId, sort])
}
/// Медиатека админки: зарегистрированные файлы /uploads/... (без обязательной привязки к товару).
model GalleryImage {
id String @id @default(cuid())
url String @unique
createdAt DateTime @default(now())
}
model User {
id String @id @default(cuid())
email String @unique
+12
View File
@@ -0,0 +1,12 @@
import { prisma } from './prisma.js'
/** Регистрация загруженных путей в медиатеке (идемпотентно). */
export async function upsertGalleryImagesByUrls(urls) {
for (const url of urls) {
await prisma.galleryImage.upsert({
where: { url },
create: { url },
update: {},
})
}
}
+2
View File
@@ -3,6 +3,7 @@ import {
parseMaterialsInput,
slugify,
} from './api/_product-helpers.js'
import { registerAdminGalleryRoutes } from './api/admin-gallery.js'
import { registerAdminCategoryRoutes } from './api/admin-categories.js'
import { registerAdminOrderRoutes } from './api/admin-orders.js'
import { registerAdminProductRoutes } from './api/admin-products.js'
@@ -22,6 +23,7 @@ export async function registerApiRoutes(fastify) {
parseMaterialsInput,
mapProductForApi,
})
await registerAdminGalleryRoutes(fastify)
await registerAdminCategoryRoutes(fastify, { slugify })
await registerAdminOrderRoutes(fastify)
await registerAdminReviewRoutes(fastify)
+47
View File
@@ -0,0 +1,47 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import { prisma } from '../../lib/prisma.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.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()
},
)
}
+2
View File
@@ -1,3 +1,4 @@
import { upsertGalleryImagesByUrls } from '../../lib/gallery.js'
import { prisma } from '../../lib/prisma.js'
import {
formatFileTooLargeMessage,
@@ -31,6 +32,7 @@ export async function registerAdminProductRoutes(
maxFiles: 10,
maxFileBytes: getProductImageMaxFileBytes(),
})
await upsertGalleryImagesByUrls(urls)
return { urls }
} catch (error) {
let message = error instanceof Error ? error.message : 'Не удалось загрузить файлы'