No tienes acceso a esta clase

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

Completando la base de datos

15/29
Recursos

Aportes 45

Preguntas 9

Ordenar por:

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

La lógica del upsert para determinar si llamar al insert o al update no está funcionando.

Tal como está el código, siempre viene información dentro del data.id (si es registro nuevo el data.id lo estamos llenando desde el controller con el id generado por nanoid).

Como desde el controlador para cualquier caso data.id viene lleno, entonces se está entrando directamente al update y nunca en el insert.

Para solventar esto yo agregué un parámetro en la función upsert que me dice si el registro es nuevo o no:

function upsert(table, data, isNew){
    if(data && isNew){
        return insert(table, data);
    }else{
        return update(table, data);
    }
}

No sé si hay alguna mejor forma de hacerlo.

Tuve un problema al generar el token debido a que el resultado de la query es un rowdatapacket. Lo solucione creando un nuevo objeto simple para que el metodo sign de jwt no me genere problemas.

function query(table, query){
    return new Promise((resolve, reject) => {
        connection.query(`SELECT * FROM ${table} WHERE ?`, query,(error, result)=>{
            if(error) return reject(error)
            
            // Necesario para evitar el rowdatapacket
            let output = {
                id: result[0].id,
                username: result[0].username,
                password: result[0].password
            }
            
            resolve(output, null)
        })
    })
}

Si cambiamos un poco la estructura de nuestra tabla de mysql (haciendo que el username sea único lo que es lo correcto), podríamos sólo hacer una función para el upsert:

const upsert = async (table, payload) => new Promise((resolve, reject) => {
  connection.query(`INSERT INTO ${table} SET ? ON DUPLICATE KEY UPDATE ?`, [payload, payload], (error, data) => {
    console.log('UPDATE DATA: ', data)
    if (error) {
      return reject(error)
    }
    resolve(data)
  })
})

Chicos hay 2 errores una vez que termina este video. Para que no pasen los mismos dolores de cabeza que yo. Les comparto los errores y su solución

Error: Expected “payload” to be a plain object
Solución: Propagar el objecto

    const login = async (username, password) => {
        let data = await store.query(TABLE, { username: username })
        
        if(!data){
            data = { password: ''}
        }

        return bcrypt.compare(password, data.password)
        .then((isValid) => {
            if(isValid){
                //TOKEN GENERATE
                **return jwt.sign({ ...data }) **         
            }else{
                throw error('Invalid information')
            }
        }).catch((err) => {
            throw error(err.message, 403)
        })
    }

*Error: ER_PARSE_ERROR: You have an error in your SQL syntax
Solución: Validar id del objeto contra la DB

const upsert = async (table, data) => {
    const row = await get(table, data.id);
    
    if (row.length === 0) {
      return insert(table, data);
    } else {
      return update(table, data);
    }
}

Este fue el código con el que puede hacer la clase. Espero que les sirva. Saludos

// SE REALIZA LA CONSULTA QUE TRAIGA TODO SOBRE LA TABLA
function list(table) {
    return new Promise( (resolve, reject) => {
        connection.query(`SELECT * FROM ${table}`, (err, data) => {
            if (err) return reject(err);
            resolve(data);
        })
    })
}
// FUNCION PARA TRAER UN USUARIO CON UN ID EN ESPECIFICO
function get(table, id) {
    return new Promise((resolve, reject) => {
        console.log("ID TO BE GET: ", id);
        console.log("IN TABLE TO GET: ", table);
        connection.query(`SELECT * FROM ${table} WHERE id=${id}`, (err, data) => {
            if (err) return reject(err);
            console.log("QUERY DONE: ", data);
            resolve(data);
        })
    })
}
// FUNCION PARA INSERTAR UN USUARIO
function insert(table, data) {
    return new Promise((resolve, reject)=>{
        console.log(`GOING TO MAKE AN INSERT INTO TABLE: ${table} with data: ${data}`)
        connection.query(`INSERT INTO ${table} SET ?`, data, (err, result) => {
            if(err) {
                console.error("### ERR ###: ",err)
                return reject(err)
            } else {
                resolve(result)
            }
        })
    })
}
// FUNCION PARA ACTUALIZAR UN USUARIO
function update(table, data) {
    return new Promise((resolve, reject)=>{
        console.log("DATA TO BE UPDATED: ",data);
        connection.query(`UPDATE ${table} SET ? WHERE id= ?`,[data, data.id],(err,result)=>{
            if(err) {
                console.error("UPDATE CANNOT BE DONE: ",err)
                return reject(err)
            } else {
                console.log("UPDATE DONE: ", result)
                resolve(result)
            }
        })
    })
}

// FUNCION PARA ACTUALIZAR MODIFICAR
// LA FUNCION  upsert VA VA A HACER LA DIFERENCIA QUE ENTRE UN INSERT Y UN UPDATE
// SI LA DATA TIENE IN data.id SERA UN UPDATE POR QUE ASI VIENEN DE
// ROUTER /api/components/user/network.js
const upsert = async (table, payload) =>
  new Promise((resolve, reject) => {
    console.log("DATA TO BE UPSERT: ", payload);
    connection.query(`INSERT INTO ${table} SET ? ON DUPLICATE KEY UPDATE ?`, [payload, payload], (error, data) => {
        console.log("UPSERT DATA: ", data);
        console.log("UPDATE TABLE: ", table);
        if (error) {
            return reject(error);
        }
        resolve(data);
    });
  });

// FUNCION ASINCRONA PARA SABER SI ESTA REGISTRADO EL USUARIO
// SE GUARDA LA LISTA PARA FILTRAR
function query(table, query) {
    return new Promise((resolve, reject) => {
        console.log("DATA TO BE QUERY: ", query);
        console.log("IN TABLE: ", table);
        connection.query(`SELECT * FROM ${table} WHERE ?`, query, (err, res) => {
            console.log('QUERY RESULT: ', res)
            if (err) return reject(err);
            resolve(res[0] || null);
        })
    })
}

module.exports = {
    list,
    get,
    upsert,
    query

};

**Puede ayudarte 🐞 :
Si alguien tiene un error como el siguiente:

Error: Expected "payload" to be a plain object.
    at validate (D:\dev\project-nodejs\node_modules\jsonwebtoken\sign.js:40:11)
    at validatePayload (D:\dev\project-nodejs\node_modules\jsonwebtoken\sign.js:62:10)
    at Object.module.exports [as sign] (D:\dev\project-nodejs\node_modules\jsonwebtoken\sign.js:114:7)
    at Object.sign (D:\dev\project-nodejs\auth\index.js:9:14)
    at bcrypt.compare.then.sonIguales (D:\dev\project-nodejs\api\components\auth\controller.js:20:23)
    at <anonymous>

❌ Esto se encuentra en el siguiente archivo: /auth/index.js en la función sign.

function sign(data) {
  return jwt.sign(data, secret);
}

✔️ Y se soluciona de la siguiente forma:

function sign(data) {
  let jsonData = JSON.parse(JSON.stringify(data));
  return jwt.sign(jsonData, secret);
}

Esto se debe a que el valor del parámetro “data” de la función no es serializable. A veces uno no sabe si el dato proviene de diferentes fuentes y no sabe si será un objeto simple o no, en ese caso puede resolverlo parseando los datos.

Estoy bloqueado con este error Expected “payload” to be a plain object. veo que es de jwt pero no se de porque.

estes mi auth.js

const jwt = require('jsonwebtoken')
const config = require('../config');

const secret = config.jwt.secret;

function sign(data) {
    data.id = parseInt(data.id.toString()); 
    return jwt.sign(data, secret)
}

function verify(token) {
    return jwt.verify(token, secret);
}
const check = {
    own: function(req, owner) {
       const decoded = decodeHader(req)
       console.log(decoded);    
    }
}
function getToken(auth) {
    if (!auth)
        throw new Error('No viene ningun token')
    
    if ( auth.indexOf('Bearer ') === -1 )
        throw new Error('Formato invalido');
    
    let token = auth.replace('Bearer ', '');
    return token;
}
function decodeHader(req) {
    const authorization = req.headers.authorization || '';
    const token = getToken(authorization)
    const decoded = verify(token);

    req.user = decoded;

    return decoded;
}
module.exports = {
    sign,
    check
}

Con respecto al error que mencionaba @nancygtec:
“La lógica del upsert para determinar si llamar al insert o al update no está funcionando”, debido a que el id para ambos casos siempre va ha existir.

Les presento mi solución, espero les sea de ayuda:

async function upsert(table, data) {
    let lista = await list(table);
    let data_id = [];
    for (let key in lista) {
        if (data.id === lista[key].id) {
            data_id.push(lista[key].id)
        }
    }
    // console.log(data_id.length)
    if (data_id.length > 0) {
        return update(table, data);
    } else {
        return insert(table, data);
    }
}

Llevo mas de dos horas y no encuentro el error en “body”: "Información Invalida"
tome algunos consejos de mis compañeros pero no sigo en lo mismo 😦

En el método get de strore/mysql.js me parece que es más seguro pasar el ID como un párametro y no concatenarlo directamente en la query ya que podría explotarse como una inyección SQL.

Así he dejado mi métod get:
SELECT * FROM ${table} WHERE id = ?, [id], …

function get(table, id) {
    return new Promise((resolve, reject) => {
        connection.query(`SELECT * FROM ${table} WHERE id = ?`, [id], (err, data) => {
            if(err) return reject(err);

            resolve(data);
        })
    });
}

a ver está chévere que se haya unificado las funcionalidades de update e insert en un solo método, pero creo que sería mejor hacer un endpoint dedicado para cada escenario y que a futuro no se le tenga que agregar más complejidad al método por lógica de negocio.
Algo que aprendí de un líder de desarrollo fue:
“A veces es mejor crecer a nivel de líneas de código que en complejidad” ❄

No me queda clara la ventaja de usar un sólo método para insertar y actualizar. Entiendo que los ORM funcionan así. Pero en nuestro caso, que no estamos usando ninguno, no deberíamos cuidar la separación de responsabilidades de los métodos?

Pueden usar db4free o freemysqlhosting como sustituto a remotemysql

Compañeras y compañeros, cuidado con lo siguiente:

Al parecer, cuando se le hace la query a la base de datos, me pasó que me devolvió la data del usuario envuelta en un objeto de tipo RowDataPacket y ese tipo de objetos no son recibidos por la función sign de jsonwebtoken. ¿Cómo se soluciona? Muy fácil, en deben utilizar el Spread Operator de JavaScript, con lo que en la parte de la función login del componente auth que hace la query queda así:

const userRaw = await store.query(TABLE, {username: username})
 userData = {...userRaw}

En este link queda una explicación mejor del spread operator.

Espero haber sido de ayuda.

Tuve el problema de que al cambiar a la base de datos de mysql no me estaba generando el token, y eso es por que la funcion query() retorna un objeto con una firma

RowDataPacket {
  id: 'HmaZav2Flmfk9GWV56oCx',
  username: 'username',
  passwd: '$2b$08$aum2gRBawvQD5N2coHLrx.xKpPpMwst1OhftAKaKfl96Py/mhIu.C'
}

y eso evita que webtoken pueda generar el token, basta con ir al index.js de auth, en la funcion sing queda así:

function sing(data){
	return jwt.sign(JSON.stringify(data), config.auth.palabraSecreta)
}

Yo tuve un error 500 porque faltaba reemplazar en el archivo auth/controller.js la referencia a la db dummy

module.exports = function (injectedStore) {
    let store = injectedStore;
    if (!store) {
        store = require('../../../store/mysql');
    }

Nota: si el id buscado para hacer update es un “varchar” la query debe ser la siguiente:
.
UPDATE ${table} SET ? WHERE id = '${id}'
.
(El id buscado debe ir entre comillas ‘id’)

Luego de terminar el vídeo con los últimos cambios, dejo de funcionar el insert.

Problemas con la lógica de upsert y el ID de la tabla
Hice una pequeña variación del código para resolver inconveniente desde el mismo componente de mysql

function upsert(table, data) {

    get(table, data.id)
        .then((result) => {
            if (result.length===0){
                return insert(table, data);
            } else {
                return update(table, data);
            }
        }).catch((err) => {
            return console.error(err)
        });
}```

Con el tema del UPSERT preferiría tener mas lineas de código pero garantizar la sencillez en el código. Por lo tanto creo que es mejor separar el UPDATE y el INSERT

Y para el error generado por el RowDataPacket, lo solucioné copiando su contenido en otra varaible:

function query(table, param){
  return new Promise((resolve, reject) => {
    connection.query(`SELECT * FROM ${table} WHERE ?`, param['query'], (error, result) => {
      if(error) return reject(error);
      const output = {
        ... result[0]
      }
      resolve(output || null);
    })
  })
}

yo tenia un problema con la promesa por lo que la modifique un poco para que me generara el codigo



async function login(username,password) {
console.log(‘ingres2o’)
console.log(username+’??’+password)
const data= await store.query(TABLA,{username:username})
console.log(data.password)
let retorna = bcrypt.compareSync(password, data.password)
console.log(retorna)
if(retornatrue){
// return auth.sign(data)
return auth.sign(JSON.parse(JSON.stringify(data)));
}
else{
throw new Error (‘info123 invalida’)
}
// bcrypt.compareSync(password, data.password)
// .then(igual=>{
// if(igual=true){
// console.log(bcrypt.compareSync(password, data.password))
// // generar token
// return auth.sign(data)
// } else{
// throw new Error (‘info123 invalida’)
// };
// });

yo tenia un problema con la promesa por lo que la modifique un poco para que me generara el codigo

async function login(username,password) {
console.log(‘ingres2o’)
console.log(username+’??’+password)
const data= await store.query(TABLA,{username:username})
console.log(data.password)
let retorna = bcrypt.compareSync(password, data.password)
console.log(retorna)
if(retornatrue){
// return auth.sign(data)
return auth.sign(JSON.parse(JSON.stringify(data)));
}
else{
throw new Error (‘info123 invalida’)
}
// bcrypt.compareSync(password, data.password)
// .then(igual=>{
// if(igual
=true){
// console.log(bcrypt.compareSync(password, data.password))
// // generar token
// return auth.sign(data)
// } else{
// throw new Error (‘info123 invalida’)
// };
// });

    // return data
}
Que el Id se esté generando desde el Controller se me hace muy raro, además este atributo no debe poder ser modificado por el usuario eso es aún más raro. Lo que hice fue cambiar el atributo id de las tablas auth y user por un int. En la tabla user decirle que sea autoincrement, así cuando se inserte un nuevo usuario no hay necesidad de pasarle un id, se genera automáticamente, pero si se necesita ese id para pasarselo a la tabla auth. Así me quedo la función upsert del Controller de user ```js async function upsert(body) { const user = { name: body.name, username: body.username, }; if (body.id) { user.id = body.id; } // Primero se inserta o actualiza en la tabla `user`. const result = await store.upsert(TABLA, user); // Si es una nueva inserción, se usa el `insertId`. const userId = user.id || result.insertId; // Ahora, realiza el upsert en `auth` con el `id` correcto. if (body.password || body.username) { await auth.upsert({ id: userId, username: user.username, password: body.password, }); } return result; } ```Las funciones insert, update y upsert así quedarían (ojo no estoy usando Promesas se me hace muy viejo ya habiendo async/await ```js async function insert(table, data) { try { const [results] = await connection.query( `INSERT INTO ${table} SET ?`, data ); return results; } catch (err) { throw err; } } async function update(table, data) { try { // Desestructurar el id y el resto de los datos a actualizar const { id, ...updateFields } = data; const [results] = await connection.query( `UPDATE ${table} SET ? WHERE id=?`, [updateFields, data.id] ); return results; } catch (err) { throw err; } } async function upsert(table, data) { if (data.id) { try { // Intentar actualizar el registro primero. const updateResult = await update(table, data); // Verificar si la actualización afectó alguna fila. Si no, significa que el `id` no existía y necesitamos insertar. if (updateResult.affectedRows === 0) { return insert(table, data); } return updateResult; } catch (err) { throw err; } } else { // Si no hay un `id`, hacer una inserción. return insert(table, data); } } ```Al principio el profe menciono que las tablas user y auth están ligadas, el id de auth además de ser primary key debe ser foreig key.
Mejor usemos un ORM jejej es menos "chamba"
Duda, es un antipatron acceder a las tablas por el nombre? Me refiero a las funciones genericas que se han venido generando en el curso y por la convencion de usar ORMs

Otro error que es posible que tengan y que vi que ya algunos solucionaron es el json que devuelva la base datos, para solucionarlos solo copie los valores a un objeto literal de la siguiente manera.

const login = async (username,password) => {
        const data = await store.query(TABLA,{username:username})
        return bcrypt.compare(password,data.password)
                    .then((sonIguales)=>{
                        
                        if(sonIguales){
                            const plainObject = {...data}
                            return auth.sign(plainObject)
                        
                        } else {

                            throw new Error('Informacion invalida')
                        }

                    })
    }

que informacion mandan en el login de postman? me regresa informacion invalida

tengo el mismo codigo del profesor y me sale este error al hacer login, a alguien mas le pasa?:

TypeError: Cannot read property ‘password’ of null
at Object.login (/Users/xxx/Documents/sites/node/proyecto-backend-node-platzi/api/components/auth/controller.js:15:46)

si sucede el problema de información invalida corrigan este archivo

componenst\auth\network.js

const express = require('express');

const response = require('../../../network/response');
const Controller = require('./index');

const router = express.Router();


router.post('/login', function(req, res, next) {
    Controller.login(req.body.username, req.body.password)
        .then(token => {
            response.success(req, res, token, 200);
        })
        .catch(next);
})

module.exports = router;

Esto te ayudará en futuras lecciones
Para los que tengan problemas con,

if (data && data.id) {
return insert(table, data);
} else {
return update(table, data);
}

Siempre estamos pasando un ID ya sea para actualizar o insertar, por ende siempre nos dará true. Para evitar este error hay muchas soluciones la mía fue añadir un elemento a los datos que se manda de nombre type cuyo valor puede ser insert o update.

Les dejo el código para la función de upsert() en el archivo mysql.js

// Guardamos el type
let type = data["type"];
// Lo eliminamos del diccionario
delete data["type"];

// Comprobamos
if (type == "insert") {
// Agregamos un usuario
return insert(table, data);
} else {
// Actualizamos un usuario
    return update(table, data);
} 

Creé una solución del upsert en una misma función

 function upsert(table, param) {
  return new Promise((resolve, reject) => {
    if (!param.data.id){
      connection.query(`INSERT INTO ${table} 
                        SET ?`, param.data, (error, result) => {
                        if (error) return reject(error);
                        resolve(result);
                      })
    }
    else{
      connection.query(`UPDATE ${table} 
      SET ? WHERE id=?`, [param.data, param.data.id], (error, result) => {
      if (error) return reject(error);
      resolve(result);
    })
    }
  }) 
 }

Quien me pueda ayudar con este erro.!

Cuando hago el agregar usuario me sale este error

code: ‘ER_BAD_FIELD_ERROR’,
errno: 1054,
sqlMessage: “Unknown column ‘password’ in ‘field list’”,
sqlState: ‘42S22’,
index: 0,
sql: “INSERT INTO users SET id = ‘DKer99SDm6Rxl2of1-vKW’, name = ‘juan’, username = ‘JuanTeix’, password = ‘1234’”

Para solucionar el error lo que hice fue tomar el objeto creado en el middleware de secure (req.user) pasarlo por parámetro a la función upsert del controlador

si depronto el codigo de: raparisg que esta abajo no les sirve

async function upsert(table, data) {
console.log(table)
console.log(data.id)

const row = await get(table, data.id);
console.log(row)
if (row.length === 0) {
  return insert(table, data);
} else {
  return update(table, data);
}

}

y les sale un error de sql es por que tienen que poner en el get la consulta entre coomillas

function get(table, id) {
return new Promise((resolve, reject) => {
connection.query(SELECT * FROM ${table} WHERE id="${id}", (err, data) => {
if (err) return reject(err);
resolve(data);
})
})
}```

Esta clase si estuvo fuerte, casi no la logro. Pero ya se pudo.

Hola Compañeros, comentando algo que me paso es que el remotemysql no me funcionaba correctamente, les recomiendo hacer la practica en modo local con xampp, o si tienen algún servidor seria mejor.

Amigos alguien me puede ayudar con este error

[error] TypeError: Cannot read property ‘password’ of null
at Object.login (/home/gerry/Projects/Practico_node/api/components/auth/controller.js:19:26)
at processTicksAndRejection

Grupo de aprendizaje para temas relacionados a Node js: https://chat.whatsapp.com/CrXPoxll2VMA4Jo7dJyP1w

Si cuando hacen el put les sale un error. Asegúrence de primero hacer una petición put con exactamente los mismos datos con los que hacen el login (id, name, username y password). Ya que hicieron eso, ahora si pueden modificar.

Tenia un problema con el return en el Query me marcaba undefinend, la técnica que utilice para solucionarlo fue destructuring mediante el paso a otro objeto let user = {…res[0]};

function query(table, query) {
  return new Promise((resolve, reject) => {
    connection.query(`SELECT * FROM ${table} WHERE ?`, query, (err, res) => {
        let user = {...res[0]};
        if (err) return reject(err);
        resolve(user || null);
    })
  })
}```

Convertir RowData a Objeto

function query(table, query) {
  return new Promise((resolve, reject) => {
      connection.query(`SELECT * FROM ${table} WHERE ?`, query, (err, res) => {
          if (err) return reject(err);
          var responseObject = res.map((result, index) => {
            return Object.assign({}, result);
          });
          resolve(responseObject[0] || null);
      })
  })
}```

https://remotemysql.com/ esta caido. que podemos hacer?

Tuve problemas con el insert y update, y lo corregí de la siguiente forma para que no sea necesario enviar todos los campos a la hora de actualizar.

Network.js User

router.post("/", (req, res, next) => {
  Controller.upsert(req.body,true)
    .then((user) => {
      response.success(req, res, user, 201);
    })
    .catch(next);
});
router.put("/", secure("update"), (req, res,next) => {
  Controller.upsert(req.body,false)
    .then((user) => {
      response.success(req, res, user, 201);
    })
    .catch(next);
});

controller.js User

async function upsert(body, isNew) {
    const authUser = {};
    const user = {};
    if (isNew) {
      user.id = uuid.v4();
    } else {
      user.id = body.id;
      authUser.id= body.id;
    }

    if (body.username) {
      user.username = body.username;
      authUser.username = body.username;
    }
    if (body.name) {
      user.name = body.name;
      authUser.name = body.name;
    }
    if (body.password) {
      authUser.password = body.password;
    }

    //crear o editar datos de tabla auth
    await auth.upsert(authUser, isNew);
    //crear o editar datos de tabla user
    return store.upsert(TABLA, user, isNew);
  }

constroller.js Auth

 async function upsert(data,isNew) {
    const authData = {
      id: data.id,
    };
   
    if (data.username) {
      authData.username = data.username;
    }
    if (data.password) {
      authData.password = await bcrypt.hash(data.password,8 );
    }
    return store.upsert(TABLA, authData, isNew);
  }

mysql.js Store

async function upsert(table, data, isNew) {
 
  if (data && isNew) {
    return insert(table, data);
  } else {
    return update(table, data);
  }
}

En el mundo profesional las operaciones de base de datos van dentro de transacciones