feat(F-048): completed feature

This commit is contained in:
chattie
2026-08-19 07:17:14 +02:00
parent 8ee1938af9
commit 835ab66eda
187 changed files with 12361 additions and 1065 deletions

View File

@@ -1,9 +1,11 @@
import type { FastifyInstance } from 'fastify';
import type { FastifySchema } from 'fastify';
import type pg from 'pg';
import { z } from 'zod';
import type { Authenticate } from '../../../shared/auth.js';
import { requireRole } from '../../../shared/auth.js';
import { AppError } from '../../../shared/errors.js';
import { errorSchema } from '../../../shared/swagger.js';
import { parseJson } from '../../../shared/http-input.js';
import { ReviewsService } from '../application/reviews-service.js';
import {
@@ -47,7 +49,14 @@ export async function registerReviewsRoutes(
const verifier = new PgOrderItemVerifier(deps.pool);
const service = new ReviewsService(repository, verifier);
app.post('/reviews', async (request, reply) => {
const submitReviewSchema: FastifySchema = {
tags: ['Reviews'],
summary: 'Submit review',
description: 'Crea una reseña. Requiere compra verificada.',
body: { type: 'object' },
response: { 201: { type: 'object' }, 401: errorSchema },
};
app.post('/reviews', { schema: submitReviewSchema }, async (request, reply) => {
const user = await deps.authenticate(request);
const input = parseJson(submitSchema, request.body);
try {
@@ -58,7 +67,18 @@ export async function registerReviewsRoutes(
}
});
app.patch('/reviews/:id/moderate', async (request, reply) => {
const moderateSwaggerSchema: FastifySchema = {
tags: ['Reviews'],
summary: 'Moderate review (admin)',
params: {
type: 'object',
required: ['id'],
properties: { id: { type: 'string', format: 'uuid' } },
},
body: { type: 'object' },
response: { 401: errorSchema, 403: errorSchema },
};
app.patch('/reviews/:id/moderate', { schema: moderateSwaggerSchema }, async (request, reply) => {
const user = await deps.authenticate(request);
requireRole(user, 'admin');
const { id } = moderateParamSchema.parse(request.params);
@@ -71,13 +91,35 @@ export async function registerReviewsRoutes(
}
});
app.get('/reviews', async (request, reply) => {
const listReviewsSchema: FastifySchema = {
tags: ['Reviews'],
summary: 'List reviews (público)',
querystring: {
type: 'object',
required: ['productId'],
properties: { productId: { type: 'string', format: 'uuid' } },
},
};
app.get('/reviews', { schema: listReviewsSchema }, async (request, reply) => {
const { productId } = productIdQuerySchema.parse(request.query);
const result = await service.listPublishedByProduct(productId);
return reply.send(result);
});
app.get('/reviews/admin', async (request, reply) => {
const adminReviewsSchema: FastifySchema = {
tags: ['Reviews'],
summary: 'List reviews (admin)',
querystring: {
type: 'object',
properties: {
status: { type: 'string', enum: ['pending', 'published', 'rejected'] },
limit: { type: 'integer', default: 20 },
offset: { type: 'integer', default: 0 },
},
},
response: { 401: errorSchema, 403: errorSchema },
};
app.get('/reviews/admin', { schema: adminReviewsSchema }, async (request, reply) => {
const user = await deps.authenticate(request);
requireRole(user, 'admin');
const { status, limit, offset } = adminListSchema.parse(request.query ?? {});

View File

@@ -9,7 +9,11 @@ export interface ReviewsRepository {
insert(review: SubmitReviewCommand): Promise<Review | undefined>;
setStatus(id: string, status: 'published' | 'rejected'): Promise<Review | undefined>;
listPublishedByProduct(productId: string): Promise<PublishedReview[]>;
listAll(params?: { status?: string; limit?: number; offset?: number }): Promise<{ items: Review[]; total: number }>;
listAll(params?: {
status?: string;
limit?: number;
offset?: number;
}): Promise<{ items: Review[]; total: number }>;
aggregate(productId: string): Promise<ProductReviewAggregate>;
findByOrderItemId(orderItemId: string): Promise<Review | undefined>;
}