This is my controller to update put user data. The controller accepts up to 4 values. I would like to do so if I send only a name to this route, This will change only the name and the rest will remain unchanged. (it will not be empty). Is it possible to do this? Do I have to do it in redux-saga, i.e. if it is empty, give it up-to-date
// Update basic User's Data
exports.setUserdata = (req, res) => {
const id = req.params.userId;
User.update(
{
password: req.body.password,
name: req.body.name,
surname: req.body.surname,
email: req.body.email,
},
{ where: { id } },
)
.then(() => {
res.status(200).json({ success: true });
})
.catch(() => {
res.status(500).json({ error: 'Internal server error' });
});
};
Pass params you want to update and don't pass other keys.
If req.body contains only name key, you can just pick up those 4 keys from req.body.
const updateParams = _.pick(req.body, ['name', 'password', 'surname', 'email'])
User.update(updateParams, { where: { id } })
If req.body has other properties with value null or undefined, you can filter them after picking.
const updateParams = _.chain(req.body).pick(['name', 'password', 'surname', 'email']).filter().value()
User.update(updateParams, { where: { id } })
Of course it depends on the ORM you are using but I believe most ORM don't update attributes which are not passed at all.
Related
So I am learning CRUD for a school project and I followed a tutorial that was really useful. However, when I completed it I noticed that when there are no more quotes to update, it still updates quotes. How can I change this so that it will stop updating quotes that arent even there?
app.put('/quotes', (req, res) => {
quoteCollection.findOneAndUpdate(
{ name: 'Yoda' },
{
$set: {
name: req.body.name,
quote: req.body.quote
}
},
{upsert: true}
)
.then(result => {
//The if block that i am trying
if (result.deletedCount === 0) {
return res.json('No quote to delete')
}
})
.catch(error => console.error(error))
})
Why are you passing {name: "Yoda}? This route is supposed to only update the quote with "Yoda" as its name? If not, then you need to grab from the request object the quote that should be updated.
I tried to create a different version, based on the assumption that the quote that should be updated will come from the req.body:
app.put("/quotes", async (req, res) => {
//Grab the name/id/identifier for the quote you want to update from the body
const query = req.body.name;
// Try to update the document on the database
try {
const result = await quoteCollection.findOneAndUpdate(
query,
{
name: req.body.name,
quote: req.body.quote,
},
{
upsert: true,
new: true,
}
);
// If it worked, it will return the updated quote
res.status(200).json({
status: 200,
data: {
result,
},
});
} catch (err) {
res.status(400).json({
status: 400,
message: "Something went wrong",
});
}
});
I'm quite newb to mongodb and mongoose, so I ask you to help me.
I've an API, which works and now I want to extend it with filtering for given params.
I've a Order model, which points to two different collections of documents Material & User Schemas and have a quantity element.
let Order = new Schema({
materials:
{
type: Array,
material: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Material'
},
qty: {
type: Number
}
},
userId: {
type: Schema.Types.ObjectId,
ref: 'User'
}
}, {
collection: 'orders'
})
Also I've method to create an order:
exports.createOrder = (req, res) => {
if (!req.body.user) {
res.status(400).send({message: 'Content can not be empty!'});
}
const order = new Order({
materials: req.body.materials,
userId: req.body.user
});
order
.save(order)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the Order."
});
});
}
If I create Order filling only Material ID, it creates and filtering by given material ID in filter request.
post request
filter request
But If I trying to point qty it isn't present in response.
post request with qty
filter request ending with previous document id
There is my question: How can I create Order exact way I need (Material ID and qty number must persist) and How can I perform a filtering operations on them?
Any help appriciated.
My mistake was in method how I create order as well as I make a filtering request.
Correct method to create order with data storing in array type is following
exports.createOrder = (req, res) => {
if (!req.body.user) {
res.status(400).send({message: 'Content can not be empty!'});
}
const order = new Order({
materials: {material: req.body.materials, qty: req.body.qty},
userId: req.body.user
});
order
.save(order)
.then(data => {
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while creating the Order."
});
});
}
as you can see, difference is how I form materials array.
next thing is in filter request
exports.filterOrder = (req, res) => {
Order.find({"materials.material": req.body.material})
.then(data => {
console.log(data);
res.send(data);
})
.catch(err => {
res.status(500).send({
message:
err.message || "Some error occurred while retrieving Orders."
});
});
}
If I need to filter orders contain necessary material I need to place subelement of array in quotes with dot notation. This will work also with "material.qty" parameter if needed.
This question does not seek answer as "best practice", but rather the more appropriate way compared to what I have below.
Basically I have a User model/table that has this associate with UserScore model/table.
User.belongsTo(models.UserScore, {
foreignKey: {
name: "userScoreId",
allowNull: false,
defaultValue: 1
},
as: "score",
targetKey: "id"
})
And then the UserScore model is defined as:
'use strict';
module.exports = (sequelize, DataTypes) => {
const UserScore = sequelize.define('UserScore', {
id: { type: DataTypes.INTEGER, allowNull: false, autoIncrement: true, unique: true, primaryKey: true },
score: DataTypes.INTEGER,
coins: DataTypes.INTEGER
}, {});
UserScore.associate = function (models) {
UserScore.hasMany(models.User, {
foreignKey: "userScoreId",
sourceKey: "id"
})
};
return UserScore;
};
The PROBLEM that I have is that I feel like I'm doing my Sequelize queries below incorrectly.
First I create UserScore assuming that I know the userId, using Facebook works because I can get id from Facebook. BUT NOT with signup with email and password! This is the first problem.
And after creating the UserScore, I then create a User data. This just feels so wrong. Although it works well with login with Facebook because like I said, I have the userId for creating UserScore, but what if I don't have it (e.g. using sign up with email and password)?
EDIT: The question is related to the error about constraints, because I tried making a User data without making a UserScore first. I can't it reproduce now because I've fixed it using UUId (https://www.npmjs.com/package/uuid-int).
AuthController.js
module.exports = {
// SIGN UP WITH EMAIL AND PASSWORD
signup: async (req, res, next) => {
const email = req.body.email
const password = req.body.password
if (!email || !password) {
return res.status(403).send({
message: "Error! Required parameters are: {email} and {password}."
})
}
// Check if there is a user with the same email
db.User.findOne({
where: { email: email }
})
.then(data => {
if (data) {
return res.status(409).send({
message: "Email is already in use."
})
}
// Create a new user...
const newUser = {
fbId: null,
email: email,
firstName: null,
lastName: null,
photoUrl: null
}
const newUserScore = {
score: 0,
userId: id, //// <---------- NO IDEA!!!
coins: 0
}
db.UserScore.create(newUserScore)
.then(userScoreData => {
db.User.create(newUser)
.then(data => {
console.log("Created new user! ✅")
// Generate the token
const token = signToken(newUser)
// Respond with token
res.status(200).json({ token })
})
.catch(err => {
console.log("Error creating a new user!!!." + err)
return res.status(409).send({
message: "An error has occured while creating a new user"
})
})
})
.catch(err => {
console.log("Error creating user score.")
done(err, null, err.message)
})
})
.catch(err => {
console.log(err)
res.status(500).send({
message: "An error has occured while retrieving data."
})
})
}
}
User Relations:
I tried making a User data without making a UserScore first.
The foreign key constraint you have defined between User and UserScore
will never allow you to add a UserScoreId in the table User that does not exist in the UserScore table, as this will violate
the SQL foreign key constraint. This is indeed a constraint !
From what I understand (correct me if I'm wrong),
you are modeling the fact that a user may have one or more score(s),
if so, it's the userId that should be in the UserScore table.
This way, if the user with the same email does not exist you can add the user first then add the user score.
So you have to change how you defined your sequelize associations
and set userId as a foreign key on the UserScore table.
Here's how you could define the associations
//will add userId to UserScore table
User.hasMany(UserScore, {
foreignKey: { name: "userId", allowNull: false, defaultValue: 1 },
as: "score",
targetKey: "id"
})
//same here, will add userId to UserScore table
UserScore.belongsTo(User, {
foreignKey: "userId",
sourceKey: "id"
})
I am using pg-promise.
I am having an issue trying to insert the following Javascript array:
[ { email: 'test1#gmail.com', password: 'test2' },
{ email: 'tes2t#gmx.com', password: 'test'3 },
{ email: 'test4#gmail.com', password: 'test4' },
{ email: 'test4#yahoo.com.ar', password: 'test5' }]
Using the following:
async function insertDB(data){
const cs = new pgp.helpers.ColumnSet(['email', 'password'], {table: 'users'});
console.log(data)
const query = pgp.helpers.insert(data, cs);
db.none(query)
.then(data => {
logger.info(" Query success: ", data);
})
.catch(error => {
logger.warn(" Query error: ", error);
});
}
and I get
UnhandledPromiseRejectionWarning: Error: Property 'password' doesn't exist.
**data.password = undefined**
**data[0] = { email: 'test1#gmail.com', password: 'test2' }**
How can I insert this data into my postgresdb?
// should create columnsets only once:
const cs = new pgp.helpers.ColumnSet(['email', 'password'], {table: 'users'});
function insertDB(data) {
// wrapping it into a function is safer, for error-reporting via query methods:
const query = ()=> pgp.helpers.insert(data, cs);
db.none(query)
.then(data => {
// data = null always here, no point displaying it
logger.info('Query success:', data);
})
.catch(error => {
logger.warn('Query error:', error);
});
}
And your function doesn't need async in this case.
UnhandledPromiseRejectionWarning: Error: Property 'password' doesn't exist.
You are confusing the JavaScript compiler, declaring the function as async, and then throwing an error synchronously while generating the insert, due to missing property password.
And if you want to insert some records without password, with null, for example, define your columnset like this:
const cs = new pgp.helpers.ColumnSet([
'email',
{name: 'password', def: null}
], {table: 'users'});
Other than that, type ColumnSet is ultimately flexible, see the documentation for each contained Column.
EXTRA
And if you want to use server-side DEFAULT value for missing passwords, you can provide it, with the help of Custom Type Formatting:
const DEFAULT = {rawType: true, toPostgres: ()=> 'DEFAULT'};
And then your password column can be defined like this:
{name: 'password', def: DEFAULT}
And there are many alternatives, with properties init and mod supported by Column.
I wrote a query that gives me posts from a table and also returns an info about each post's author:
SELECT post.id, post.text, post.datetime, JSON_OBJECT(
'username', user.username,
'firstName', user.firstName,
'firstName', user.lastName) as author
FROM post
INNER JOIN user ON post.authorId = user.id;
But in response the author field is a string:
author: "{"username": "#", "firstName": null}"
datetime: "2017-05-02T20:23:23.000Z"
id: 10
text: "5555"
I tried to fix that using CAST but anyway author is a string:
CAST(JSON_OBJECT(
'username', user.username,
'firstName', user.firstName,
'firstName', user.lastName) as JSON) as author
Why is it happened and how to fix that?
UPD:
I send the data from server using Node.js and Express:
app.get('/posts', (req, res, next) => {
getPosts().then((posts) => {
res.setHeader('Content-Type', 'application/json');
res.send(posts);
})
.catch(next);
});
// ...
getPosts() {
return new Promise((resolve, reject) => {
const query = `
SELECT post.id, post.text, post.datetime, JSON_OBJECT(
'username', user.username,
'firstName', user.firstName,
'firstName', user.lastName) as author
FROM post
INNER JOIN user ON post.authorId = user.id;`;
this.connection.query(query, (err, result) => {
if(err) {
return reject(new Error("An error occured getting the posts: " + err));
}
console.log(result) // prints author as a string
resolve(result || []);
});
});
}
Result of console.log:
{
id: 1,
text: 'hello, world!',
datetime: 2017-05-02T15:08:34.000Z,
author: '{"username": "#", "firstName": null}'
}
I also tried here change res.send(posts) to res.json(posts) but it's doesn't help.
My function from client that touch server for the posts:
export const getPosts = () => {
customFetch(apiUrl + '/posts')
.then(response => response.json())
.then(json => json)
};
I think it's fine for MySQL to return a string, as the JSON_OBJECT() function is already doing its job by producing a string that represents a well formatted JSON.
You can convert that string to a JSON object in javascript with
var obj = JSON.parse(yourString);
Edit (about JSON and Javascript objects)
First of all, in case you didn't know, JSON stands for JavaScript Object Notation: that means that it's a textual way of representing JavaScript objects.
From MySQL point of view, you're already solving this problem inside the SELECT statement, because what the query is returning is a valid JSON.
The fact is that then that data is transmitted to Javascript (Node), but Javascript internal representation of an object is not the same as its textual representation (the JSON); this means you have to "cast" it, so that the string gets converted to the object.
The mechanism you'd need in order to avoid this cast would require MySQL to know how Javascript represents an object, and then using such knowledge to return the bytecode of your object. This is called serialization, and I'm afraid it's way beyond the purpose of a dbms like MySQL.
Hope this clarifies your doubts a bit...
Used JSON typeCast in the DB config file
connection: {
..
host: process.env.DB_HOST,
user: process.env.DB_USER,
..
typeCast: function (field, next) {
if (field.type == 'JSON') {
return (JSON.parse(field.string()));
}
return next();
},
..
}
What about returning the whole thing as a JSON_OBJECT and doing JSON.Parse on it once
SELECT JSON_OBJECT("id", post.id, "text", post.text, "datetime", post.datetime, "author", JSON_OBJECT(
'username', user.username,
'firstName', user.firstName,
'firstName', user.lastName))
FROM post
INNER JOIN user ON post.authorId = user.id;
That eliminates your need to loop