Merge branch 'refactor'
This commit is contained in:
@@ -5,7 +5,7 @@ import {
|
||||
} from '../../lib/default-category.js'
|
||||
import { prisma } from '../../lib/prisma.js'
|
||||
|
||||
export async function registerAdminCategoryRoutes(fastify, { slugify } = {}) {
|
||||
export async function registerAdminCategoryRoutes(fastify) {
|
||||
fastify.get(
|
||||
'/api/admin/categories',
|
||||
{ preHandler: [fastify.verifyAdmin] },
|
||||
@@ -27,7 +27,7 @@ export async function registerAdminCategoryRoutes(fastify, { slugify } = {}) {
|
||||
reply.code(400).send({ error: 'Укажите название категории' })
|
||||
return
|
||||
}
|
||||
const slug = String(body.slug ?? '').trim() || slugify(name) || `cat-${Date.now()}`
|
||||
const slug = String(body.slug ?? '').trim() || request.server.slugify(name) || `cat-${Date.now()}`
|
||||
if (isUnspecifiedCategorySlug(slug)) {
|
||||
reply.code(400).send({ error: `Slug «${UNSPECIFIED_CATEGORY_SLUG}» зарезервирован` })
|
||||
return
|
||||
|
||||
@@ -8,19 +8,59 @@ import {
|
||||
} from '../../lib/upload-limits.js'
|
||||
import { persistMultipartImages } from '../../lib/upload-images.js'
|
||||
|
||||
export async function registerAdminProductRoutes(
|
||||
fastify,
|
||||
{ slugify, parseMaterialsInput, mapProductForApi } = {},
|
||||
) {
|
||||
const CREATE_PRODUCT_SCHEMA = {
|
||||
body: {
|
||||
type: 'object',
|
||||
required: ['title', 'priceCents'],
|
||||
properties: {
|
||||
title: { type: 'string', minLength: 1 },
|
||||
slug: { type: 'string' },
|
||||
categoryId: { type: 'string' },
|
||||
priceCents: { type: 'number', minimum: 0 },
|
||||
quantity: { type: 'number', minimum: 0 },
|
||||
inStock: { type: 'boolean' },
|
||||
leadTimeDays: { type: 'number', minimum: 1 },
|
||||
shortDescription: { type: 'string' },
|
||||
description: { type: 'string' },
|
||||
materials: { anyOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }] },
|
||||
imageUrl: { type: 'string' },
|
||||
imageUrls: { type: 'array', items: { type: 'string' } },
|
||||
published: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const PATCH_PRODUCT_SCHEMA = {
|
||||
body: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
title: { type: 'string', minLength: 1 },
|
||||
slug: { type: 'string' },
|
||||
categoryId: { type: 'string' },
|
||||
priceCents: { type: 'number', minimum: 0 },
|
||||
quantity: { type: 'number', minimum: 0 },
|
||||
inStock: { type: 'boolean' },
|
||||
leadTimeDays: { type: 'number', minimum: 1 },
|
||||
shortDescription: { type: 'string' },
|
||||
description: { type: 'string' },
|
||||
materials: { anyOf: [{ type: 'array', items: { type: 'string' } }, { type: 'string' }] },
|
||||
imageUrl: { type: 'string' },
|
||||
imageUrls: { type: 'array', items: { type: 'string' } },
|
||||
published: { type: 'boolean' },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export async function registerAdminProductRoutes(fastify) {
|
||||
fastify.get(
|
||||
'/api/admin/products',
|
||||
{ preHandler: [fastify.verifyAdmin] },
|
||||
async () => {
|
||||
async (request) => {
|
||||
const items = await prisma.product.findMany({
|
||||
include: { category: true, images: { orderBy: { sort: 'asc' } } },
|
||||
orderBy: { updatedAt: 'desc' },
|
||||
})
|
||||
return items.map(mapProductForApi)
|
||||
return items.map((p) => request.server.mapProductForApi(p))
|
||||
},
|
||||
)
|
||||
|
||||
@@ -52,7 +92,7 @@ export async function registerAdminProductRoutes(
|
||||
|
||||
fastify.post(
|
||||
'/api/admin/products',
|
||||
{ preHandler: [fastify.verifyAdmin] },
|
||||
{ preHandler: [fastify.verifyAdmin], schema: CREATE_PRODUCT_SCHEMA },
|
||||
async (request, reply) => {
|
||||
const body = request.body ?? {}
|
||||
const title = String(body.title ?? '').trim()
|
||||
@@ -60,7 +100,7 @@ export async function registerAdminProductRoutes(
|
||||
reply.code(400).send({ error: 'Укажите название' })
|
||||
return
|
||||
}
|
||||
const slug = String(body.slug ?? '').trim() || slugify(title) || `item-${Date.now()}`
|
||||
const slug = String(body.slug ?? '').trim() || request.server.slugify(title) || `item-${Date.now()}`
|
||||
let categoryId = String(body.categoryId ?? '').trim()
|
||||
if (!categoryId) {
|
||||
categoryId = (await getOrCreateUnspecifiedCategory()).id
|
||||
@@ -115,7 +155,7 @@ export async function registerAdminProductRoutes(
|
||||
shortDescription: body.shortDescription ? String(body.shortDescription) : null,
|
||||
description: body.description ? String(body.description) : null,
|
||||
quantity,
|
||||
materials: JSON.stringify(parseMaterialsInput(body.materials)),
|
||||
materials: JSON.stringify(request.server.parseMaterialsInput(body.materials)),
|
||||
priceCents: Math.round(priceCents),
|
||||
imageUrl: body.imageUrl ? String(body.imageUrl) : null,
|
||||
published: Boolean(body.published),
|
||||
@@ -134,13 +174,13 @@ export async function registerAdminProductRoutes(
|
||||
},
|
||||
include: { category: true, images: { orderBy: { sort: 'asc' } } },
|
||||
})
|
||||
reply.code(201).send(mapProductForApi(product))
|
||||
reply.code(201).send(request.server.mapProductForApi(product))
|
||||
},
|
||||
)
|
||||
|
||||
fastify.patch(
|
||||
'/api/admin/products/:id',
|
||||
{ preHandler: [fastify.verifyAdmin] },
|
||||
{ preHandler: [fastify.verifyAdmin], schema: PATCH_PRODUCT_SCHEMA },
|
||||
async (request, reply) => {
|
||||
const { id } = request.params
|
||||
const body = request.body ?? {}
|
||||
@@ -182,7 +222,7 @@ export async function registerAdminProductRoutes(
|
||||
data.quantity = Math.floor(n)
|
||||
}
|
||||
if (body.materials !== undefined) {
|
||||
data.materials = JSON.stringify(parseMaterialsInput(body.materials))
|
||||
data.materials = JSON.stringify(request.server.parseMaterialsInput(body.materials))
|
||||
}
|
||||
if (body.priceCents !== undefined) {
|
||||
const p = Number(body.priceCents)
|
||||
@@ -254,7 +294,7 @@ export async function registerAdminProductRoutes(
|
||||
data: { ...data, images: imagesUpdate },
|
||||
include: { category: true, images: { orderBy: { sort: 'asc' } } },
|
||||
})
|
||||
return mapProductForApi(product)
|
||||
return request.server.mapProductForApi(product)
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
import { prisma } from '../../lib/prisma.js'
|
||||
|
||||
const PUBLIC_PRODUCTS_QUERY_SCHEMA = {
|
||||
querystring: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
categorySlug: { type: 'string' },
|
||||
q: { type: 'string' },
|
||||
availability: { type: 'string', enum: ['all', 'in_stock', 'made_to_order'] },
|
||||
sort: { type: 'string', enum: ['', 'price_asc', 'price_desc'] },
|
||||
page: { type: 'integer', minimum: 1 },
|
||||
pageSize: { type: 'integer', minimum: 1, maximum: 100 },
|
||||
priceMin: { type: 'number', minimum: 0 },
|
||||
priceMax: { type: 'number', minimum: 0 },
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const EMPTY_REVIEWS_SUMMARY = Object.freeze({
|
||||
approvedReviewCount: 0,
|
||||
avgRating: null,
|
||||
@@ -58,12 +74,13 @@ export async function approvedReviewSummariesForProducts(productIds) {
|
||||
return map
|
||||
}
|
||||
|
||||
export async function registerPublicCatalogRoutes(fastify, { mapProductForApi } = {}) {
|
||||
export async function registerPublicCatalogRoutes(fastify) {
|
||||
fastify.get('/api/categories', async () => {
|
||||
return prisma.category.findMany({ orderBy: { sort: 'asc' } })
|
||||
})
|
||||
|
||||
fastify.get('/api/products', async (request, reply) => {
|
||||
fastify.get('/api/products', { schema: PUBLIC_PRODUCTS_QUERY_SCHEMA }, async (request, reply) => {
|
||||
const { mapProductForApi } = request.server
|
||||
const { categorySlug } = request.query
|
||||
const qRaw = request.query?.q
|
||||
const q = typeof qRaw === 'string' ? qRaw.trim() : ''
|
||||
@@ -134,7 +151,7 @@ export async function registerPublicCatalogRoutes(fastify, { mapProductForApi }
|
||||
|
||||
const summaries = await approvedReviewSummariesForProducts(items.map((it) => it.id))
|
||||
return {
|
||||
items: items.map((p) => mapProductForApi(p, summaries.get(p.id) ?? EMPTY_REVIEWS_SUMMARY)),
|
||||
items: items.map((p) => request.server.mapProductForApi(p, summaries.get(p.id) ?? EMPTY_REVIEWS_SUMMARY)),
|
||||
total,
|
||||
page,
|
||||
pageSize,
|
||||
@@ -152,7 +169,7 @@ export async function registerPublicCatalogRoutes(fastify, { mapProductForApi }
|
||||
return
|
||||
}
|
||||
const summaries = await approvedReviewSummariesForProducts([product.id])
|
||||
return mapProductForApi(product, summaries.get(product.id) ?? EMPTY_REVIEWS_SUMMARY)
|
||||
return request.server.mapProductForApi(product, summaries.get(product.id) ?? EMPTY_REVIEWS_SUMMARY)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user