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,8 +1,10 @@
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 { AppError } from '../../../shared/errors.js';
import { errorSchema } from '../../../shared/swagger.js';
import { parseJson } from '../../../shared/http-input.js';
import type { InventoryServicePort } from '../../inventory/index.js';
import type { PricingServicePort } from '../../pricing/index.js';
@@ -38,12 +40,24 @@ export async function registerCartRoutes(
deps.promotions,
);
app.get('/cart', async (request, reply) => {
const cartSchema: FastifySchema = {
tags: ['Cart'],
summary: 'Get cart',
description: 'Devuelve el carrito del usuario autenticado.',
response: { 401: errorSchema },
};
app.get('/cart', { schema: cartSchema }, async (request, reply) => {
const user = await deps.authenticate(request);
return reply.send(serializeCart(await service.getCart(user.id)));
});
app.post('/cart/items', async (request, reply) => {
const addItemSchema: FastifySchema = {
tags: ['Cart'],
summary: 'Add item to cart',
body: { type: 'object' },
response: { 201: { type: 'object' }, 401: errorSchema },
};
app.post('/cart/items', { schema: addItemSchema }, async (request, reply) => {
const user = await deps.authenticate(request);
const input = parseJson(itemBodySchema, request.body);
try {
@@ -53,7 +67,22 @@ export async function registerCartRoutes(
}
});
app.patch('/cart/items/:variantId', async (request, reply) => {
const updateItemSchema: FastifySchema = {
tags: ['Cart'],
summary: 'Change item quantity',
params: {
type: 'object',
required: ['variantId'],
properties: { variantId: { type: 'string', format: 'uuid' } },
},
body: {
type: 'object',
required: ['quantity'],
properties: { quantity: { type: 'integer', minimum: 1 } },
},
response: { 401: errorSchema },
};
app.patch('/cart/items/:variantId', { schema: updateItemSchema }, async (request, reply) => {
const user = await deps.authenticate(request);
const { variantId } = parseJson(variantParamSchema, request.params);
const { quantity } = parseJson(quantityBodySchema, request.body);
@@ -64,13 +93,33 @@ export async function registerCartRoutes(
}
});
app.delete('/cart/items/:variantId', async (request, reply) => {
const removeItemSchema: FastifySchema = {
tags: ['Cart'],
summary: 'Remove item from cart',
params: {
type: 'object',
required: ['variantId'],
properties: { variantId: { type: 'string', format: 'uuid' } },
},
response: { 401: errorSchema },
};
app.delete('/cart/items/:variantId', { schema: removeItemSchema }, async (request, reply) => {
const user = await deps.authenticate(request);
const { variantId } = parseJson(variantParamSchema, request.params);
return reply.send(serializeCart(await service.removeItem(user.id, variantId)));
});
app.post('/cart/promo-code', async (request, reply) => {
const promoSchema: FastifySchema = {
tags: ['Cart'],
summary: 'Apply promo code',
body: {
type: 'object',
required: ['code'],
properties: { code: { type: 'string', maxLength: 64 } },
},
response: { 401: errorSchema },
};
app.post('/cart/promo-code', { schema: promoSchema }, async (request, reply) => {
const user = await deps.authenticate(request);
const { code } = parseJson(promoCodeBodySchema, request.body);
try {

View File

@@ -32,9 +32,10 @@ export class CartService {
}
async applyPromoCode(userId: string, code: string): Promise<CartView> {
if (!this.promotions) return this.toView(await this.carts.setPromoCode(userId, code));
await this.promotions.validateCode(code);
return this.toView(await this.carts.setPromoCode(userId, code.trim().toUpperCase()));
const normalizedCode = code.trim().toUpperCase();
if (!this.promotions) return this.toView(await this.carts.setPromoCode(userId, normalizedCode));
await this.promotions.validateCode(normalizedCode);
return this.toView(await this.carts.setPromoCode(userId, normalizedCode));
}
private async toView(

View File

@@ -1,5 +1,21 @@
/** Public API of the cart module. */
import type pg from 'pg';
import type { InventoryServicePort } from '../inventory/index.js';
import type { PricingServicePort } from '../pricing/index.js';
import type { PromotionServicePort } from '../promotions/index.js';
import { CartService } from './application/cart-service.js';
import { PgCartRepository } from './infrastructure/pg-cart-repository.js';
export { registerCartRoutes, type CartRoutesDeps } from './api/cart.routes.js';
export { CartService } from './application/cart-service.js';
export type { Cart, CartItem, CartItemInput, CartItemView, CartView } from './domain/cart.js';
export type { CartRepository } from './domain/ports.js';
export function createCartService(
pool: pg.Pool,
pricing: PricingServicePort,
inventory: InventoryServicePort,
promotions?: PromotionServicePort,
): CartService {
return new CartService(new PgCartRepository(pool), pricing, inventory, promotions);
}