No tienes acceso a esta clase

隆Contin煤a aprendiendo! 脷nete y comienza a potenciar tu carrera

Obteniendo 贸rdenes del perfil

13/20
Recursos

Aportes 15

Preguntas 4

Ordenar por:

驴Quieres ver m谩s aportes, preguntas y respuestas de la comunidad?

o inicia sesi贸n.

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;
  }

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;

Hola a todos.
Estaba recibiendo este error:

Y esto se remonta al curso pasado de 鈥淣ode 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 鈥渋tems鈥 entonces que proceda con su respectiva validacion de 鈥渢his.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 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);
    }
  }
);

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;
  }

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;
  }

馃搵 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 鈥渟ub鈥.

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 鈥淏earer鈥 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 鈥淪ervers鈥, 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"
			}
		}
	}
]

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, 鈥渦ser鈥 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 (: