No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Obteniendo órdenes del perfil

13/20
Recursos

Aportes 18

Preguntas 5

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Reto
En order.router.js
En el body se enviaría el sub del payload como body.

// POST
router.post('/',
  passport.authenticate('jwt', { session: false }),
  async (req, res, next) => {
    try {
      const body = {
        userId: req.user.sub
      }
      const newOrder = await service.create(body);
      res.status(201).json(newOrder);
    } catch (err) {
      next(err);
    }
  }
);

Luego en order.service.js Modificamos el create() para que la inserción sea automatizada con solo enviar el sub o userId, hacemos una busqueda findOne al customer Where user .id sea igual a data.userId. Donde este se almacenará en customer el cual tendremos que extraer el ID para enviarlo al create de order. Si no se encuentra se regresa un no encontrado.

async create(data) {
    const customer = await models.Customer.findOne({
      where: {
        '$user.id$': data.userId
      },
      include: ['user']
    })
    if (!customer) {
      throw boom.badRequest('Customer not found');
    }
    const newOrder = await models.Order.create({ customerId: customer.id });
    return newOrder;
  }

Hola a todos.
Estaba recibiendo este error:

Y esto se remonta al curso pasado de “Node con Postgres” donde yo continue con mi progreso.
Si realizaba hacer un post de una orden o consultar todas las ordenes, me salia el mismo error.
Esto se corrige poniendo una condicional en el modelo de ordenes en el campo virtual total. Ponemos una condicional que diga si exista “items” entonces que proceda con su respectiva validacion de “this.items.length”

total: {
    type: DataTypes.VIRTUAL,
    get() {
      //Reviso si tenemos productos
      if(this.items) {
        console.log('existen items en la orden')
        if(this.items.length > 0) {
          return this.items.reduce((total, item) => {
            return total + (item.price * item.OrderProduct.amount)
          }, 0)
        }
        return 0
      }
    }
  }

Para poder ver las órdenes de compra de un usuario podemos usar el token que tiene, obtener el sub y obtener la información directamente sin necesidad de enviar el ID del usuario.

Se crea un nuevo método en el servicio orders, el método findByUser recibe el userId y realiza una consulta respecto a éste donde se incluye la asociación de user y customer, y se desea obtener el id del customer ya que únicamente se cuenta con el id de usuario, es por ello que se utiliza where: {'$customer.user.id$': userId}, es decir, le estamos diciendo a qué asociaciones estamos haciendo la consulta.

Más información sobre este tipo de consultas https://sequelize.org/master/manual/eager-loading.html#complex-where-clauses-at-the-top-level

async findByUser(userId) {
    const orders = await models.Order.findAll({
      where: {
        '$customer.user.id$': userId,
      },
      include: [
        {
          association: 'customer',
          include: ['user'],
        },
      ],
    });
    return orders;
  }

Se crea un nuevo router profile.router.js, aquí se obtiene el id del usuario que está en este momento con sesión (const orders = await service.findByUser(user.sub)), el id está en el sub del payload. Es decir, ya no es necesario enviar el user id porque ya viene en el token y se va a obtener de ahí.

const express = require('express');
const passport = require('passport');

const OrderService = require('../../services/order.service');

const router = express.Router();
const service = new OrderService();

router.get(
  '/my-orders',
  passport.authenticate('jwt', { session: false }),
  async (req, res, next) => {
    try {
      const user = req.user;
      const orders = await service.findByUser(user.sub);
      res.json(orders);
    } catch (error) {
      next(error);
    }
  }
);

module.exports = router;

Agregamos la nueva ruta a routes/index.js:

const express = require('express');

const productsRouter = require('./api/products.route');
const usersRouter = require('./api/users.router');
const categoriesRouter = require('./api/categories.route');
const orderRouter = require('./api/orders.router');
const customerRouter = require('./api/customer.route');
const authRouter = require('./api/auth.route');
const profileRouter = require('./api/profile.router');

function routerApi(app) {
  const router = express.Router();
  app.use('/api', router);
  router.use('/products', productsRouter);
  router.use('/users', usersRouter);
  router.use('/categories', categoriesRouter);
  router.use('/orders', orderRouter);
  router.use('/customers', customerRouter);
  router.use('/auth', authRouter);
  router.use('/profile', profileRouter);
}

module.exports = routerApi;

Para resolver el reto de crear la order sin neesidad de enviar el customerId en el body de la petición hariamos lo siguente:

En order.router.js

router.post(
  '/',
  passport.authenticate('jwt', { session: false }),
  //validatorHandler(createOrderSchema, 'body'),
  async (req, res, next) => {
    try {
      const body = {
        userId: req.user.sub
      };
      const order = await service.create(body);
      res.status(201).json(order);
    } catch (error) {
      next(error);
    }
  }
);

Hay que eliminar el validator porque ahora no es necesario pasar en el body el customer id, pero se agrega el jwt validator

Luego en order.service.js

  async create(data) {
    // Accedemos al modelo Customer y usando where encadenamos hacia user
    const customer = await models.Customer.findAll({
      where: {
        '$user.id$': data.userId
      },
      include: ['user']
    });
    // Validamos que exista el customer
    if (!customer) {
      throw boom.notFound('Customer not found');
    }
    // Creamos un objeto con el customerId obtenido de la consulta
    const dataOrder = {
      customerId: customer[0].id
    };
    const newOrder = await models.Order.create(dataOrder);
    return newOrder;
  }

TRas un poco de depuración con este metodo vi que los resultados de la consulta llegan en un Array, si se canbia el FinAll por findOne en ese caso solo retorna un unico objeto y no haria falta usar el customer[0] sino directamente customer

Para crear una orden necesitamos el customerID ¿Cómo obtenerlo del token?


  1. Agregué la autenticación por token en el post de Orders
  2. Modifiqué el schema de Orders para que customerId ya no sea solicitado.
  3. Construí una función en los servicios de Customer que encuentre un Customer que contenga el mismo userId que le paso como argumento.
  4. En el Router de Orders importé los servicios de Customer y usé la función que me devuelve el customer en función al userId que obtengo de res.user.sub
  5. El Body a enviar lo he de-construído y he agregado un customerId: customer. id.

En customer.service.js

  async findByUser(userId) {
    const customer = await models.Customer.findOne({
      where: { userId: userId }
    });
    if (!customer) {
      throw boom.notFound("Sorry, user not found");
    }
    return customer;
  }

En orders.router.js:

router.post(
  "/",
  passport.authenticate("jwt", { session: false }),
  validatorHandler(createOrderSchema, "body"),
  async (req, res, next) => {
    try {
      const customer = await serviceCustomer.findByUser(req.user.sub);
      const body = req.body;
      const newOrder = await service.create({
        ...body,
        customerId: customer.id
      });
      res.status(201).json({ newOrder });
    } catch (error) {
      next(error);
    }
  }
);

📋 Clase #13: Obteniendo órdenes del perfil 13/20 📋


 
Se quiere obtener las ordenes del usuario sin necesidad de enviar el id del usuario, para ello se usa el token del usuario cuando hizo login que a su vez tiene un identificador llamado “sub”.
 
Pasos:
 

  • Vamos a VSC, abrimos la carpeta services y abrimos el archivo order.service.js, se implementa la lógica para la función buscar por usuario: findByUser (mas info: aquí), el código queda:
     
async findByUser(userId) {
	//Se hace la consulta a pesar que se tiene el id de user y no de customer
	const orders = await models.Order.findAll({
		//Cuando hay tipos de estas asociaciones, se usa: where
		where: {
			'$customer.user.id$': userId
		},
		include: [
			{
			//Customer tiene una relación con user (relación de tablas)
			association: 'customer',
			//Con user se puede filtrar esas ordenes de compra asociadas a ese usuario y a customer
			include: ['user']
			}
		]
	});
	return orders;
}

 
Guardamos, ahora necesitamos crear una nueva ruta, vamos a la carpeta routes y creamos el archivo profile.routes.js, se implementará la lógica para obtener las ordenes del usuario pasándole el sub del payload y no el id del user, el código queda:
 

const express=require('express');
const passport=require('passport');
const OrderService=require('../services/order.service');

const router=express.Router();
const service=new OrderService();

router.get('/my-orders',
	passport.authenticate('jwt',{session:false}),
	async(req,res,next)=>{
		try{
			//Necesitamos el user para obtener el identificador sub
			const user=req.user;
			//Las ordenes de compra
			const orders=await service.findByUser(user.sub);
			res.json(orders);
		}catch(error){
			next(error);
		}
	}
);

module.exports=router;

 
Guardamos, necesitamos actualizar el index.js de la carpeta routes, se agrega después de authRouter:
 

const profileRouter = require('./profile.router');

 
La ruta dentro de routerApi queda:
 

router.use('/profile', profileRouter);

 
Guardamos, si no se ha compilado con: npm run dev en la terminal, esperar a Mi port 3000
 

  • Vamos a Insomnia, entramos a la carpeta Auth, duplicamos el Login y lo editamos y colocamos Get orders y la solicitud en GET, la dirección: _.API_URL/api/v1/profile/my-orders, antes de consultar, en la pestaña de Auth, se selcciona “Bearer” y en TOKEN se pega el token del usuario autenticado en Auth. Si le damos a Send, debe salir un código 200 OK y como no se han hecho registros de ordenes, nos sale un array vacío: []
     
  • Para llenar el array de las ordenes de compra, debemos añadir las ordenes. Para ello necesitamos saber el id del usuario, pero necesitamos el id del customer porque lo creamos desde esa tabla, para conocer ese Id, vamos al navegador, entramos a pgAdmin.
     
  • Cuando entramos a pgAdmin, al lado izquierdo hay un panel donde sale “Servers”, al darle click sale una lista de jerarquía desplegable, ir a MyStore > Databases > my_store > Schemas > public > Tables > customers, le damos con el botón derecho del mouse sobre customers y seleccionamos View/Edit Data > All Rows, aparece la tabla con todos los customers, buscamos el usuario del token e identificamos en la primera columna el id.
     
  • Vamos a Insomnia y dentro de la carpeta Orders, seleccionamos POST Create orders (dirección: _ . API_URL/api/v1/orders) y en el body de JSON se coloca el id que identificamos en pgAdmin, en mi caso:
     
{
	"customerId": 2
}

 
Para agregar productos a la orden, debemos tener algunos productos creados, en caso de que no se hayan creado, vamos a la carpeta de Products, seleccionamos POST Create products (dirección: _ . API_URL/api/v1/products) y vamos creando productos solo editando el name (se puede editar el resto de items si se desea), por ejemplo:
 

{
	"name": "Product 2",
	"price": "30",
	"description": "example and test 2",
	"image": "http: // placeimg. com/640/480",
	"categoryId": 2
}

 
Al dar Send, nos va arrojando un código 201 Created cada vez que creamos un producto:
 

{
	"createdAt": "2023-03-29T02:29:31.823Z",
	"id": 2,
	"name": "Product 2",
	"price": 30,
	"description": "example and test 2",
	"image": "http: // placeimg. com/640/480",
	"categoryId": 2
}

 

  • Ahora vamos a la carpeta de Orders y seleccionamos POST Create Items (dirección: _ . API_URL/api/v1/orders/add-item), se debe especificar los siguientes items:
     
{
	"orderId": 2,
	"productId": 1,
	"amount": 2
}

 
Al dar Send, nos arroja un código 201 Creted:
 

{
	"createdAt": "2023-03-29T02:30:21.591Z",
	"id": 2,
	"orderId": 2,
	"productId": 1,
	"amount": 2
}

 

  • Si agregamos varios items y queremos consultarlos todos, vamos a la carpeta de Auth en Get orders y al dar Send, nos arroja un código 200 OK y todos los items con su respectiva información:
     
[
	{
		"total": 0,
		"id": 1,
		"customerId": 2,
		"createdAt": "2023-03-29T02:22:51.691Z",
		"customer": {
			"id": 2,
			"name": "Pedro",
			"lastName": "Vicente",
			"phone": "9898",
			"createdAt": "2023-03-28T21:32:16.237Z",
			"userId": 4,
			"user": {
				"id": 4,
				"email": "pedro @mail. com",
				"password": "$2b$10$2IC2hbfyNR2jMaKE71ZNZOazZy0EeMN/kzb6I7/v1PgN5/7350cqu",
				"role": "customer",
				"createdAt": "2023-03-28T21:32:16.237Z"
			}
		}
	},
	{
		"total": 0,
		"id": 2,
		"customerId": 2,
		"createdAt": "2023-03-29T02:23:17.224Z",
		"customer": {
			"id": 2,
			"name": "Pedro",
			"lastName": "Vicente",
			"phone": "9898",
			"createdAt": "2023-03-28T21:32:16.237Z",
			"userId": 4,
			"user": {
				"id": 4,
				"email": "pedro @mail. com",
				"password": "$2b$10$2IC2hbfyNR2jMaKE71ZNZOazZy0EeMN/kzb6I7/v1PgN5/7350cqu",
				"role": "customer",
				"createdAt": "2023-03-28T21:32:16.237Z"
			}
		}
	}
]

 

Reto 😄…
.
order.schema: Quitamos el schema de creación.

const Joi = require('joi');

const id = Joi.number().integer();
const orderId = Joi.number().integer();
const productId = Joi.number().integer();
const amount = Joi.number().integer().min(1);

const getOrderSchema = Joi.object({
  id: id.required(),
});

const addItemSchema = Joi.object({
  orderId: orderId.required(),
  productId: productId.required(),
  amount: amount.required(),
});

module.exports = { getOrderSchema, addItemSchema };

order.route: Añadimos passport, modificamos el post y quitamos el schema de creación.

const passport = require('passport');

const {
  getOrderSchema,
  addItemSchema,
} = require('./../schemas/order.schema');

router.post(
  '/',
  passport.authenticate('jwt', { session: false }),
  async (req, res, next) => {
    try {
      const user = req.user;
      const newOrder = await service.create(user.sub);
      res.status(201).json(newOrder);
    } catch (error) {
      next(error);
    }
  }
);

order.service: Modificamos el método create.

async create(userId) {
    const customer = await models.Customer.findOne({
      where: {
        '$user.id$': userId,
      },
      include: ['user'],
    });

    const newOrder = await models.Order.create({ customerId: customer.id });
    return newOrder;
  }
Este es el reto finalizado, sin embargo al ver que varios compañeros tenian que comentar el validor del schema vi que no deberia ser correcto ya que se está eliminando capas de seguridad. Este es mi aporte: \- orders.router.js ```router.post(  '/',  passport.authenticate('jwt', { session: false }),  checkRoles('admin', 'customer'),  validatorHandler(createOrderSchema, 'body'),  async (req, res, next) => {    try {      const userId = req.user;      const body = req.body;      const newOrder = await service.create(body, userId.sub);      res.status(201).json(newOrder);    } catch (error) {      next(error);    }  });``` \- orders.service.js ```async findCustomerId(userId) {    const getCustomerId = await models.Customer.findAll({      where: {        user\_id: userId,      },    });    return getCustomerId\[0].id;  } async create(data, userId) {    const customer = await this.findCustomerId(userId);    const newOrder = await models.Order.create({      ...data,      customerId: customer,    });    return newOrder;  } ```
Pero en la tabla customer tenemos el, id del usuario no

Yo solucione el reto de la siguiente manera.

  1. Elimina el esquema para la creación de ordenes
  2. El siguiente paso viene en lo que seria la ruta orders
const user = req.user;
const rta = await service.create(user);
  1. Luego en tu servicio orders
async create(user) {
  const rta = await models.Order.create({ customerId: user.sub });
  return rta;
}

Bueno, si esto no te llegara a funcionar, sería porque yo estoy trabajando este proyecto de otra manera pero estoy casi seguro de que funcionaria para todos los casos 😅

Mi solucion al reto
orders.router.js

router.post(
  '/',
  passport.authenticate('jwt', { session: false }),
  async (req, res, next) => {
    try {
      const user=req.user;
      const newOrder = await service.create(user.sub);
       res.status(201).json(newOrder);
    } catch (error) {
      next(error);
    }
  },
);

order.service.js

async create(userId) {
   
    const customer=await models.Customer.findOne({
      where:{'$user.id$':userId},
      include:[
        {
          association:'user',
        }
      ]
    })
    const customerId=customer.id;
    const newOrder = await models.Order.create({"customerId":customerId});
    return newOrder;
  }

Para crear la orden opté por hacer una subconsulta basada en el userId. Me parece que puede ser beneficioso a escala, pues cuando traemos el customer por separado y luego la inserción tendríamos 2 consultas a la db, y nos generan algo de latencia (sobretodo si la db esta en un server remoto).

async create(userId) {
    const order = await models.Order.create({
      customerId: sequelize.literal(`(SELECT id FROM customers WHERE user_id = ${userId})`),
    });
    return order;
  }

¡Hola compañeros!
Esta fue mi solución:

  • En orders.router quité el middleware de verificación y, envíe el sub en el create():
...
router.post(
  '/',
  passport.authenticate('jwt', {session: false}),
  async (req, res, next) => {
    try {
      const user = req.user;
      const newOrder = await service.create(user.sub);
      res.status(201).json(newOrder);
    } catch (error) {
      next(error);
    }
  }
);
...
  • En orders.service, primero obtuve el customer y luego conseguí el customerId de este:
class OrderService {

  ...

  async create(userId) {
    const customer = await this.findOne(userId)
    const customerId = {
      customerId: await customer.dataValues.customerId
    }
    const newOrder = await models.Order.create(customerId);
    return newOrder;
  }

...

El reto me costo un poquito, por un error que tenia, veo en los aportes que muchos lo hicieron de una forma distinta a mi, utilizando una variable body y asignándole un objeto, etc, les dejo lo que hice yo, de una forma diferente

Dentro de orders.router.js

router.post('/',
    passport.authenticate('jwt', {session: false}),
    checkRoles('customer'),
    // validatorHandler(createOrderSchema, 'body'),
    async (req, res) => {
    const user = req.user;
    const newOrder = await service.create(user.sub)
    res.status(201).json(newOrder)
});

Únicamente se le pasa como parámetro el user.sub directamente, se elimina (la comente para dejarla como repaso) validatorHandler, ya que no se enviara nada por el body en este caso

order.service.js

async create(userId){
        const user = await this.findByUser(parseInt(userId));
        const customerId = user[0].dataValues.customerId
        const newOrder = await models.Order.create({
            customerId
        })
        return newOrder;    
    }

Por alguna razon, “user” me regresa los datos como un array, no entiendo bien porque, es por eso que coloco user[0] para que me de los datos sin array, y con eso ya se envia el customerId automaticamente por medio de la sesion

Para eliminar la contraseña al hacer el request de la orden de compra utilizamos el siguiente código:
Sí tienen una mejor solución, pueden comentarla.

async findOrderByUser (userId) {
    const orders = await models.Order.findAll({
      where: {
        '$customer.user.id$': userId
      },
      include: [
        {
          association: 'customer',
          include: [{
            model: User,
            as: 'user',
            attributes: {
              exclude: ['password']
            }
          }]
        }
      ]
    })
    return orders
  }
async findOneOrder (id) {
    const order = await models.Order.findByPk(id, {
      include: [
        {
          association: 'customer',
          include: [{
            model: User,
            as: 'user',
            attributes: {
              exclude: ['password']
            }
          }]
        },
        'items'
      ]
    })
    if (!order) {
      throw boom.notFound('Order not found')
    }
    return order
  }

En mi caso enlace directamente la orden al usuario, pero yo creo que según la idea de negocio no debería permitir crear ordenes si ya tiene una activa, en mi modelo puse un campo int para validar 0 creada, 1 cancelada, 3 pagada, por lo que en el servicio primero consulto si cuenta con una orden creada para permitir crear mas ordenes…!

  // Crea una Order y la retorna
  async create(user) {
    const body = {userId: user};
    if (await this.getOrderUserActive(user) !== 0) {
      throw boom.conflict('This user have a order pending..!')
    }
    console.log(body)
    const newOrder = await models.Order.create(body)
    return await this.getOne(newOrder.id);
  }

  // Verifica que el usuario no tenga ordenes activas
  async getOrderUserActive(user){
    const order = await models.Order.findAndCountAll({
      where: {
        user_id: user,
        state: 0
      }
    })
    return order.count
  }

Al momento de tomar el usuario del sub del JWT hay que recordar en el schema colocar que el user id es opcional o simplemente no usar el validador…!

RETO

order.router.js

router.post(
  '/',
  passport.authenticate('jwt', { session: false }),
  async (req, res, next) => {
    try {
      const body = { userId: req.user.sub };
      const newOrder = await service.create(body);
      res.status(201).json(newOrder);
    } catch (error) {
      next(error);
    }
  }
);

order.services.js

async create(data) {
    const customer = await models.Customer.findOne({
      where: {
        '$user.id$': data.userId,
      },
      include: ['user'],
    });

    data.customerId = customer.id;
    delete data.userId;

    const newOrder = await models.Order.create(data);
    return newOrder;
  }

Para el reto, creamos una nueva ruta, post:

router.post("/my-orders",
    passport.authenticate('jwt', { session: false }),
    async(req, res, next) => {
        const data = { id: req.user.sub }

        try {
            res.status(201).json(await service.createFromProfile(data));
        } catch (error) {
            next(error);
        }
    }
);

Para que esto funcione, necesitamos que el servicio tenga una nueva función:

   async createFromProfile(data) {
        const user = await service.findOne(data.id);
        const customerId = user.customer.id;
        return models.Order.create({ customerId });
    }


Y listo, con eso debe estar funcionando (: