|
|
|
|
@@ -1,10 +1,12 @@
|
|
|
|
|
import { performance } from 'node:perf_hooks';
|
|
|
|
|
import type { FastifyInstance } from 'fastify';
|
|
|
|
|
import type { FastifySchema } from 'fastify';
|
|
|
|
|
import type pg from 'pg';
|
|
|
|
|
import { z } from 'zod';
|
|
|
|
|
import { requireRole, type Authenticate } from '../../../shared/auth.js';
|
|
|
|
|
import { AppError } from '../../../shared/errors.js';
|
|
|
|
|
import { parseJson } from '../../../shared/http-input.js';
|
|
|
|
|
import { errorSchema } from '../../../shared/swagger.js';
|
|
|
|
|
import {
|
|
|
|
|
AttachProductImage,
|
|
|
|
|
DetachProductImage,
|
|
|
|
|
@@ -121,7 +123,7 @@ const imageUrlSchema = z
|
|
|
|
|
|
|
|
|
|
const newImageSchema = z.object({
|
|
|
|
|
url: imageUrlSchema,
|
|
|
|
|
altText: z.string().trim().min(1).max(300),
|
|
|
|
|
altText: z.string().trim().max(300).optional().default(''),
|
|
|
|
|
role: z.enum(PRODUCT_IMAGE_ROLES),
|
|
|
|
|
variantId: z.uuid().optional().nullable(),
|
|
|
|
|
position: z.number().int().min(0).optional(),
|
|
|
|
|
@@ -180,7 +182,21 @@ export async function registerCatalogRoutes(
|
|
|
|
|
const reorderImages = new ReorderProductImages(images);
|
|
|
|
|
|
|
|
|
|
// Admin: list all products (any state)
|
|
|
|
|
app.get('/catalog/products', async (request, reply) => {
|
|
|
|
|
const catalogListSchema: FastifySchema = {
|
|
|
|
|
tags: ['Admin Products'],
|
|
|
|
|
summary: 'List all products (admin)',
|
|
|
|
|
description: 'Lista todos los productos incluyendo los no activos. Requiere rol admin.',
|
|
|
|
|
querystring: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
properties: {
|
|
|
|
|
q: { type: 'string', description: 'Búsqueda por nombre' },
|
|
|
|
|
limit: { type: 'integer', minimum: 1, maximum: 100, default: 20 },
|
|
|
|
|
offset: { type: 'integer', minimum: 0, default: 0 },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
response: { 401: errorSchema, 403: errorSchema },
|
|
|
|
|
};
|
|
|
|
|
app.get('/catalog/products', { schema: catalogListSchema }, async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const q = (request.query as { q?: string }).q;
|
|
|
|
|
@@ -190,7 +206,18 @@ export async function registerCatalogRoutes(
|
|
|
|
|
return reply.send({ items: result.items.map((p) => serializeProduct(p)), total: result.total });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.get('/productos/:slug', async (request, reply) => {
|
|
|
|
|
const publicProductSchema: FastifySchema = {
|
|
|
|
|
tags: ['Catalog'],
|
|
|
|
|
summary: 'Get product by slug (público)',
|
|
|
|
|
description: 'Devuelve un producto activo con todas sus imágenes.',
|
|
|
|
|
params: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['slug'],
|
|
|
|
|
properties: { slug: { type: 'string', description: 'URL slug del producto' } },
|
|
|
|
|
},
|
|
|
|
|
response: { 404: errorSchema },
|
|
|
|
|
};
|
|
|
|
|
app.get('/productos/:slug', { schema: publicProductSchema }, async (request, reply) => {
|
|
|
|
|
const { slug } = parseJson(slugParamSchema, request.params);
|
|
|
|
|
const product = await getBySlug.execute(slug);
|
|
|
|
|
if (!product) {
|
|
|
|
|
@@ -200,8 +227,18 @@ export async function registerCatalogRoutes(
|
|
|
|
|
return reply.send(serializeProduct(product, productImages));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// Admin: get product by ID (for editor)
|
|
|
|
|
app.get('/products/:id', async (request, reply) => {
|
|
|
|
|
const adminGetProductSchema: FastifySchema = {
|
|
|
|
|
tags: ['Admin Products'],
|
|
|
|
|
summary: 'Get product by ID (admin)',
|
|
|
|
|
description: 'Obtiene un producto por su UUID (incluye no activos). Requiere admin.',
|
|
|
|
|
params: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['id'],
|
|
|
|
|
properties: { id: { type: 'string', format: 'uuid' } },
|
|
|
|
|
},
|
|
|
|
|
response: { 401: errorSchema, 403: errorSchema, 404: errorSchema },
|
|
|
|
|
};
|
|
|
|
|
app.get('/products/:id', { schema: adminGetProductSchema }, async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const { id } = parseJson(idParamSchema, request.params);
|
|
|
|
|
@@ -213,7 +250,23 @@ export async function registerCatalogRoutes(
|
|
|
|
|
return reply.send(serializeProduct(product, productImages));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.get('/products/search', async (request, reply) => {
|
|
|
|
|
const searchSchema: FastifySchema = {
|
|
|
|
|
tags: ['Catalog'],
|
|
|
|
|
summary: 'Search products (público)',
|
|
|
|
|
description: 'Búsqueda full-text con filtros opcionales por marca y categoría.',
|
|
|
|
|
querystring: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
properties: {
|
|
|
|
|
q: { type: 'string', maxLength: 200, description: 'Consulta de búsqueda' },
|
|
|
|
|
brandSlug: { type: 'string', description: 'Slug de marca' },
|
|
|
|
|
categorySlug: { type: 'string', description: 'Slug de categoría' },
|
|
|
|
|
limit: { type: 'integer', minimum: 1, maximum: 100, default: 20 },
|
|
|
|
|
offset: { type: 'integer', minimum: 0, default: 0 },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
response: { 200: { type: 'object', properties: { items: { type: 'array' } } } },
|
|
|
|
|
};
|
|
|
|
|
app.get('/products/search', { schema: searchSchema }, async (request, reply) => {
|
|
|
|
|
const input = parseJson(searchQuerySchema, request.query);
|
|
|
|
|
const startedAt = performance.now();
|
|
|
|
|
const items = await searchProducts.execute(input);
|
|
|
|
|
@@ -234,16 +287,36 @@ export async function registerCatalogRoutes(
|
|
|
|
|
return reply.send({ items: items.map((product) => serializeProduct(product)) });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.get('/products/suggest', async (request, reply) => {
|
|
|
|
|
const { q } = parseJson(
|
|
|
|
|
z.object({ q: z.string().min(2).max(200) }),
|
|
|
|
|
request.query,
|
|
|
|
|
);
|
|
|
|
|
const suggestSchema: FastifySchema = {
|
|
|
|
|
tags: ['Catalog'],
|
|
|
|
|
summary: 'Search suggestions',
|
|
|
|
|
description: 'Devuelve correcciones sugeridas para la consulta de búsqueda.',
|
|
|
|
|
querystring: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['q'],
|
|
|
|
|
properties: { q: { type: 'string', minLength: 2, maxLength: 200 } },
|
|
|
|
|
},
|
|
|
|
|
response: {
|
|
|
|
|
200: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
properties: { suggestions: { type: 'array', items: { type: 'string' } } },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
app.get('/products/suggest', { schema: suggestSchema }, async (request, reply) => {
|
|
|
|
|
const { q } = parseJson(z.object({ q: z.string().min(2).max(200) }), request.query);
|
|
|
|
|
const suggestions = await suggestCorrections.execute(q);
|
|
|
|
|
return reply.send({ suggestions });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.post('/products', async (request, reply) => {
|
|
|
|
|
const createProductSchema: FastifySchema = {
|
|
|
|
|
tags: ['Admin Products'],
|
|
|
|
|
summary: 'Create product',
|
|
|
|
|
description: 'Crea un nuevo producto. Requiere rol admin.',
|
|
|
|
|
body: { type: 'object' },
|
|
|
|
|
response: { 201: { type: 'object' }, 401: errorSchema, 403: errorSchema },
|
|
|
|
|
};
|
|
|
|
|
app.post('/products', { schema: createProductSchema }, async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const input = parseJson(newProductSchema, request.body);
|
|
|
|
|
@@ -255,19 +328,49 @@ export async function registerCatalogRoutes(
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.get('/products/:id/variants', async (request, reply) => {
|
|
|
|
|
const variantsSchema: FastifySchema = {
|
|
|
|
|
tags: ['Admin Products'],
|
|
|
|
|
summary: 'List product variants',
|
|
|
|
|
description: 'Lista las variantes de un producto.',
|
|
|
|
|
params: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['id'],
|
|
|
|
|
properties: { id: { type: 'string', format: 'uuid' } },
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
app.get('/products/:id/variants', { schema: variantsSchema }, async (request, reply) => {
|
|
|
|
|
const { id } = parseJson(idParamSchema, request.params);
|
|
|
|
|
const items = await listVariants.execute(id);
|
|
|
|
|
return reply.send({ items: items.map(serializeVariant) });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.get('/products/:id/images', async (request, reply) => {
|
|
|
|
|
const imagesSchema: FastifySchema = {
|
|
|
|
|
tags: ['Admin Products'],
|
|
|
|
|
summary: 'List product images',
|
|
|
|
|
params: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['id'],
|
|
|
|
|
properties: { id: { type: 'string', format: 'uuid' } },
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
app.get('/products/:id/images', { schema: imagesSchema }, async (request, reply) => {
|
|
|
|
|
const { id } = parseJson(idParamSchema, request.params);
|
|
|
|
|
const items = await listImages.execute(id);
|
|
|
|
|
return reply.send({ items: items.map(serializeImage) });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.post('/products/:id/images', async (request, reply) => {
|
|
|
|
|
const attachImageSchema: FastifySchema = {
|
|
|
|
|
tags: ['Admin Products'],
|
|
|
|
|
summary: 'Attach image to product',
|
|
|
|
|
params: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['id'],
|
|
|
|
|
properties: { id: { type: 'string', format: 'uuid' } },
|
|
|
|
|
},
|
|
|
|
|
body: { type: 'object' },
|
|
|
|
|
response: { 201: { type: 'object' }, 401: errorSchema, 403: errorSchema, 404: errorSchema },
|
|
|
|
|
};
|
|
|
|
|
app.post('/products/:id/images', { schema: attachImageSchema }, async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const { id } = parseJson(idParamSchema, request.params);
|
|
|
|
|
@@ -283,27 +386,70 @@ export async function registerCatalogRoutes(
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.delete('/products/:id/images/:imageId', async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const { id, imageId } = parseJson(imageParamSchema, request.params);
|
|
|
|
|
const deleted = await detachImage.execute(id, imageId);
|
|
|
|
|
if (!deleted) {
|
|
|
|
|
throw new AppError(404, 'NOT_FOUND', 'Image not found');
|
|
|
|
|
}
|
|
|
|
|
return reply.code(204).send();
|
|
|
|
|
});
|
|
|
|
|
const deleteImageSchema: FastifySchema = {
|
|
|
|
|
tags: ['Admin Products'],
|
|
|
|
|
summary: 'Delete product image',
|
|
|
|
|
params: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['id', 'imageId'],
|
|
|
|
|
properties: {
|
|
|
|
|
id: { type: 'string', format: 'uuid' },
|
|
|
|
|
imageId: { type: 'string', format: 'uuid' },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
response: { 204: { type: 'null' }, 401: errorSchema, 403: errorSchema, 404: errorSchema },
|
|
|
|
|
};
|
|
|
|
|
app.delete(
|
|
|
|
|
'/products/:id/images/:imageId',
|
|
|
|
|
{ schema: deleteImageSchema },
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const { id, imageId } = parseJson(imageParamSchema, request.params);
|
|
|
|
|
const deleted = await detachImage.execute(id, imageId);
|
|
|
|
|
if (!deleted) {
|
|
|
|
|
throw new AppError(404, 'NOT_FOUND', 'Image not found');
|
|
|
|
|
}
|
|
|
|
|
return reply.code(204).send();
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
app.patch('/products/:id/images/reorder', async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const { id } = parseJson(idParamSchema, request.params);
|
|
|
|
|
const { items } = parseJson(reorderImagesSchema, request.body);
|
|
|
|
|
const ordered = await reorderImages.execute(id, items);
|
|
|
|
|
return reply.send({ items: ordered.map(serializeImage) });
|
|
|
|
|
});
|
|
|
|
|
const reorderImagesSwaggerSchema: FastifySchema = {
|
|
|
|
|
tags: ['Admin Products'],
|
|
|
|
|
summary: 'Reorder product images',
|
|
|
|
|
params: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['id'],
|
|
|
|
|
properties: { id: { type: 'string', format: 'uuid' } },
|
|
|
|
|
},
|
|
|
|
|
body: { type: 'object' },
|
|
|
|
|
response: { 401: errorSchema, 403: errorSchema },
|
|
|
|
|
};
|
|
|
|
|
app.patch(
|
|
|
|
|
'/products/:id/images/reorder',
|
|
|
|
|
{ schema: reorderImagesSwaggerSchema },
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const { id } = parseJson(idParamSchema, request.params);
|
|
|
|
|
const { items } = parseJson(reorderImagesSchema, request.body);
|
|
|
|
|
const ordered = await reorderImages.execute(id, items);
|
|
|
|
|
return reply.send({ items: ordered.map(serializeImage) });
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
app.post('/products/:id/variants', async (request, reply) => {
|
|
|
|
|
const createVariantSchema: FastifySchema = {
|
|
|
|
|
tags: ['Admin Products'],
|
|
|
|
|
summary: 'Create product variant',
|
|
|
|
|
params: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['id'],
|
|
|
|
|
properties: { id: { type: 'string', format: 'uuid' } },
|
|
|
|
|
},
|
|
|
|
|
body: { type: 'object' },
|
|
|
|
|
response: { 201: { type: 'object' }, 401: errorSchema, 403: errorSchema },
|
|
|
|
|
};
|
|
|
|
|
app.post('/products/:id/variants', { schema: createVariantSchema }, async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const { id } = parseJson(idParamSchema, request.params);
|
|
|
|
|
@@ -319,23 +465,53 @@ export async function registerCatalogRoutes(
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.patch('/products/:id/variants/:variantId', async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const { id, variantId } = parseJson(variantParamSchema, request.params);
|
|
|
|
|
const patch = parseJson(variantPatchSchema, request.body);
|
|
|
|
|
try {
|
|
|
|
|
const variant = await updateVariant.execute(id, variantId, patch);
|
|
|
|
|
if (!variant) {
|
|
|
|
|
throw new AppError(404, 'NOT_FOUND', 'Variant not found');
|
|
|
|
|
const updateVariantSchema: FastifySchema = {
|
|
|
|
|
tags: ['Admin Products'],
|
|
|
|
|
summary: 'Update product variant',
|
|
|
|
|
params: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['id', 'variantId'],
|
|
|
|
|
properties: {
|
|
|
|
|
id: { type: 'string', format: 'uuid' },
|
|
|
|
|
variantId: { type: 'string', format: 'uuid' },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
body: { type: 'object' },
|
|
|
|
|
response: { 401: errorSchema, 403: errorSchema, 404: errorSchema },
|
|
|
|
|
};
|
|
|
|
|
app.patch(
|
|
|
|
|
'/products/:id/variants/:variantId',
|
|
|
|
|
{ schema: updateVariantSchema },
|
|
|
|
|
async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const { id, variantId } = parseJson(variantParamSchema, request.params);
|
|
|
|
|
const patch = parseJson(variantPatchSchema, request.body);
|
|
|
|
|
try {
|
|
|
|
|
const variant = await updateVariant.execute(id, variantId, patch);
|
|
|
|
|
if (!variant) {
|
|
|
|
|
throw new AppError(404, 'NOT_FOUND', 'Variant not found');
|
|
|
|
|
}
|
|
|
|
|
return reply.send(serializeVariant(variant));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw mapProductError(error);
|
|
|
|
|
}
|
|
|
|
|
return reply.send(serializeVariant(variant));
|
|
|
|
|
} catch (error) {
|
|
|
|
|
throw mapProductError(error);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
app.patch('/products/:id/rich-data', async (request, reply) => {
|
|
|
|
|
const richDataSchema2: FastifySchema = {
|
|
|
|
|
tags: ['Admin Products'],
|
|
|
|
|
summary: 'Set product rich data',
|
|
|
|
|
description: 'Establece datos ricos: ingredientes, alérgenos, nutrición, orgánico.',
|
|
|
|
|
params: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['id'],
|
|
|
|
|
properties: { id: { type: 'string', format: 'uuid' } },
|
|
|
|
|
},
|
|
|
|
|
body: { type: 'object' },
|
|
|
|
|
response: { 401: errorSchema, 403: errorSchema, 404: errorSchema },
|
|
|
|
|
};
|
|
|
|
|
app.patch('/products/:id/rich-data', { schema: richDataSchema2 }, async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const { id } = parseJson(idParamSchema, request.params);
|
|
|
|
|
@@ -347,7 +523,18 @@ export async function registerCatalogRoutes(
|
|
|
|
|
return reply.send(serializeRichData(data));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
app.patch('/products/:id', async (request, reply) => {
|
|
|
|
|
const updateProductSchema: FastifySchema = {
|
|
|
|
|
tags: ['Admin Products'],
|
|
|
|
|
summary: 'Update product',
|
|
|
|
|
params: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['id'],
|
|
|
|
|
properties: { id: { type: 'string', format: 'uuid' } },
|
|
|
|
|
},
|
|
|
|
|
body: { type: 'object' },
|
|
|
|
|
response: { 401: errorSchema, 403: errorSchema, 404: errorSchema },
|
|
|
|
|
};
|
|
|
|
|
app.patch('/products/:id', { schema: updateProductSchema }, async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const { id } = parseJson(idParamSchema, request.params);
|
|
|
|
|
@@ -363,8 +550,25 @@ export async function registerCatalogRoutes(
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// PATCH /products/:id/state — change product state
|
|
|
|
|
app.patch('/products/:id/state', async (request, reply) => {
|
|
|
|
|
const setStateSchema: FastifySchema = {
|
|
|
|
|
tags: ['Admin Products'],
|
|
|
|
|
summary: 'Change product state',
|
|
|
|
|
description: 'Cambia el estado del producto (draft, active, archived).',
|
|
|
|
|
params: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['id'],
|
|
|
|
|
properties: { id: { type: 'string', format: 'uuid' } },
|
|
|
|
|
},
|
|
|
|
|
body: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['state'],
|
|
|
|
|
properties: {
|
|
|
|
|
state: { type: 'string', enum: ['draft', 'active', 'archived', 'pending_review'] },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
response: { 401: errorSchema, 403: errorSchema, 404: errorSchema },
|
|
|
|
|
};
|
|
|
|
|
app.patch('/products/:id/state', { schema: setStateSchema }, async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const { id } = parseJson(idParamSchema, request.params);
|
|
|
|
|
@@ -376,8 +580,17 @@ export async function registerCatalogRoutes(
|
|
|
|
|
return reply.send(serializeProduct(product));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// DELETE /products/:id
|
|
|
|
|
app.delete('/products/:id', async (request, reply) => {
|
|
|
|
|
const deleteProductSchema: FastifySchema = {
|
|
|
|
|
tags: ['Admin Products'],
|
|
|
|
|
summary: 'Delete product',
|
|
|
|
|
params: {
|
|
|
|
|
type: 'object',
|
|
|
|
|
required: ['id'],
|
|
|
|
|
properties: { id: { type: 'string', format: 'uuid' } },
|
|
|
|
|
},
|
|
|
|
|
response: { 204: { type: 'null' }, 401: errorSchema, 403: errorSchema },
|
|
|
|
|
};
|
|
|
|
|
app.delete('/products/:id', { schema: deleteProductSchema }, async (request, reply) => {
|
|
|
|
|
const user = await deps.authenticate(request);
|
|
|
|
|
requireRole(user, 'admin');
|
|
|
|
|
const { id } = parseJson(idParamSchema, request.params);
|
|
|
|
|
|