En la clase anterior vimos como puedes hacer para identificar un rol y de acuerdo a eso poder darle acceso a un endpoint, ahora te pongo el siguiente escenario:
¿Como crear un endpoint que retorne todas las órdenes relacionados con el usuario que tiene sesión?
Una solución válida a este problema es que puedes crear un endpoint que enviándole el ID de un usuario te retorne dichas órdenes, algo como esto:
http://localhost:3000/users/1/orders
Este endpoint me retornaría todas las órdenes del usuario con ID 1, sin embargo puede ser algo peligroso, es decir, este endpoint solo debería estar disponible para los roles administrativos, pero no para los usuarios porque si conoces el ID de algún cliente podrías obtener la información de órdenes de compra de un usuario.
La solución más práctica para este caso es crear un endpoint de este tipo:
http://localhost:3000/profile/my-orders
Como ves en el endpoint anterior no estás colocando de forma explícita el ID de un usuario. ¿Entonces como sabe a qué usuario le pertenecen las órdenes? Sencillo, sí nuestro usuario ya tiene una sesión, esta información ya está en el JWT que se le otorgó a ese usuario al autentificarse en el sistema así con eso podemos inferirlo de acuerdo a la sesión
Ok, ok, cerebrito, suena bien pero, ¿y el código...? Vamos a iniciar con ello. Lo primero es que vamos a hacer es crear un nuevo controlador para todos las solicitudes que sean de este tipo. Lo vamos a crear con el nombre ProfileController.
nest g co users/controllers/profile --flat
Allí vamos a exponer un nuevo endpoint que va a recibir la solicitud y gracias al decorador de
@UseGuards(JwtAuthGuard, RolesGuard) nos aseguramos que tenga un token válido, así que en ese método recogemos la información de usuario que tiene esa sesión así:
@Roles(Role.CUSTOMER)@Get('my-orders')getOrders(@Req() req:Request){const user = req.userasPayloadToken;returnthis.orderService.findOne(user.sub);}
Simplemente con decirle que dentro del método que queremos la información del Request podemos obtener la información del usuario que tiene sesión, luego simplemente obtenemos el ID y ya con eso podemos crear un método en nuestro servicio de órdenes que las retorne, algo así:
A mi no me traía las ordenes del cliente, debido a que el JWT el sub viene firmado con el id del usuario y no del customer.
Mi solución fue la siguiente:
Inyecté el repositorio de usuario en el constructor de OrdersService:
Type 'number' is not assignable to type 'boolean | FindOperator<any> | FindOptionsWhere<Customer> | FindOptionsWhere<Customer>[] | EqualOperator<Customer>'.
El método del servicio está mal y no retornará nunca nada. La solución es la siguiente
ordersByCustomer(customerId: number){returnthis.orderRepo.find({customer: customerId });//👈🏻 acá está el problema}
Ya que el 'orderRepo' está tipado con el modelo de Order. Entonces hay que indicarle que filtre por alguno de estos parámetros. En nuestro caso queremos que traiga todas las órdenes del customer que haga la petición(recuerden que el id viene vía JWT)
// auth/current-user.decorator.tsimport{ createParamDecorator,ExecutionContext,PreconditionFailedException,}from'@nestjs/common';exportconstCurrnetUser=createParamDecorator((_:unknown, ctx:ExecutionContext)=>{const request = ctx.switchToHttp().getRequest();const{ user }= request;if(!user){thrownewPreconditionFailedException('User not authenticated.');}return user;},);
Uso:
// user/profile.controller.ts// ... agrega UseGuards(JwtAuthGuard)...@UseGuards(JwtGuard,RolesGuard)@Controller('profile')exportclassProfileController{// ... en el método de tu controlador, hacer: .. @Get()myMethod(@CurrentUser() user:PayloadToken){returnthis.myService.findOne(user.sub);}}
:)
Para mongo:
Para realizar este ejercicio hay un error de logica, primero deben agregar el objeto de "Customer" como propiedad referenciada, es una relacion 1:1 asi que no hay problema.
La estructura del controlador no tiene errores asi que esta todo en orden, pero la consulta la debemos cambiar porque en si establecimos una relacio en el paso anterior hasta ahora:
// order.service.tsasyncordersByCustomer(userId: string){/*Nos traemos el usuario debido a que es quien contiene la referencia del customer.*/const user =await(awaitthis.userService.getUser(userId)).toJSON();/*Hacemos la consulta en base al customer ID*/returnawaitthis.orderModel// como el id es un objeto debemos transformarlo a un string para poder comparar, sino da error..find({customer: user.customer._id.toString()}).populate('products')// populamos la consulta.exec();}
Puede que tengan un error de tipado, esto es debido a que el sub de nuestro PayloadToken es de tipo number y los "Id" de mongo son ObjectId o Strings, asi que debemos cambiarlo tambien:
Para realizar una consulta anidada es mejor utilizar:
returnthis.orderRepo.find({ where:{ customer:{ id: customerId,},}, relations:['customer'],});```En vez de realizar la consulta por separado, espero les sirva