Google Sheets API NodeJS - Update cell without changing format - node.js

Have written NodeJS code to Insert a Row in a Google Sheet using the API and updating the row values.
I insert the row with inheritFromBefore: true, which is working fine (I tested it by commenting out the updateOption). But while updating the cell the original format is not getting preserved; rather it is changing to default blank format.
function getSheetRow(jobData) {
const jobID = jobData.getJobID();
const jobName = jobData.getJobName();
const company = jobData.getCompany();
const pmName = jobData.getPMName();
let rowData = [
{ userEnteredValue: { stringValue: jobID } },
{ userEnteredValue: { stringValue: jobName + " (" + company + ", " + pmName + ")" } }
];
return rowData;
}
async function googleSheetInsertRow(workbookID, sheetNum, insertRowIndex, jobData) {
let sheetIdMap = await googleSheetsGetSheetIDTitle(workbookID);
let rowData = getSheetRow(jobData);
return new Promise((resolve, reject) => {
const insertOption = {
insertDimension: {
range: {
sheetId: sheetNum,
dimension: 'ROWS',
startIndex: insertRowIndex,
endIndex: insertRowIndex + 1
},
inheritFromBefore: true
}
};
const updateOption = {
updateCells: {
rows: [{
values: rowData
}],
fields: '*',
start: {
sheetId: sheetNum,
rowIndex: insertRowIndex,
columnIndex: 0
}
}
};
getGSheetsAPI().spreadsheets.batchUpdate({
spreadsheetId: workbookID,
resource: {
requests: [
insertOption, updateOption
]
}
},
(er, re) => {
if (er) {
console.log(`Unable to insert new row into ${googleSheetName} : https://docs.google.com/spreadsheets/d/${workbookID}`);
return reject(er);
}
else {
console.log(`New row inserted into ${googleSheetName} : https://docs.google.com/spreadsheets/d/${workbookID}`);
return resolve();
}
}
);
});
}
[In the picture below] I have inserted a row in between Row 6 & 8. And Row 7 is inheriting the format from the row before, but while updating the cell value it is not getting preserved.
Looking for help. Thanks.

I think that in your issue, when userEnteredValue or userEnteredValue.stringValue is used to fields, the issue can be resolved. When you want to use the values except for stringValue, I would like to recommend to use userEnteredValue as the fields.
From:
const updateOption = {
updateCells: {
rows: [{
values: rowData
}],
fields: '*',
start: {
sheetId: sheetNum,
rowIndex: insertRowIndex,
columnIndex: 0
}
}
};
To:
const updateOption = {
updateCells: {
rows: [
{
values: rowData,
},
],
fields: "userEnteredValue", // <--- Modified
start: {
sheetId: sheetNum,
rowIndex: insertRowIndex,
columnIndex: 0,
},
},
};
Reference:
UpdateCellsRequest

Related

Quota exceeded Sheets API write requests per minute. Node.js

I'm having quota issues with the Sheets API provided by Google (using node.js). I am already using batch requests and have had my write quota increased to 900 per minute via a written request, but I still this error (question at the bottom after explanation):
What I am trying to do is generate a sports league schedule for a chosen amount of weeks (spreadsheets). Each week has 3 separate games (sheets). The user would first just choose how many weeks:
When the user clicks "Yes" for confirming the schedule, spreadsheets are created for whatever the chosen amount is:
Each spreadsheet gets 3 sheets created for the 3 different time slots and the team roster data is copied into the sheets:
Edit 3: I'm using the following code to try and generate these spreadsheets:
// Create Result Sheets
exports.createGoogleResultsSheets = async (req,res) => {
console.log("createGoogleResultsSheets");
const season = req.body.teamData[0].stats[0].season;
// console.log(season);
const resultSeasonFolders = await getChildFiles(resultParentFolderID);
// console.log(resultSeasonFolders);
const exists = propertyExists(resultSeasonFolders,'name',season);
// Create season folder if it does not exist yet and get its id
let seasonResultsFolderId = null;
if (exists) {
// console.log("Season Exists");
const result = resultSeasonFolders.filter(folder=>folder.name == season).map(folder=>folder.id);
seasonResultsFolderId = result[0];
} else {
// console.log("Season does not exist");
// Create Season Folder
let fileMetadata = {
'name': season,
'mimeType': 'application/vnd.google-apps.folder',
'parents' : [resultParentFolderID]
};
const result = await drive.files.create({auth: jwtClient, resource: fileMetadata});
// console.log(result);
seasonResultsFolderId = result.data.id;
}
// console.log("Folder ID:");
// console.log(seasonResultsFolderId);
var i = 0;
for (const week of req.body.schedule) {
//console.log(util.inspect(week, false, null, true));
i++;
// create sheet for each week
let fileMetadata = {
'name': "Week-"+i,
'mimeType': 'application/vnd.google-apps.spreadsheet',
'parents' : [seasonResultsFolderId]
};
let result = await drive.files.create({auth: jwtClient, resource: fileMetadata});
let spreadsheetId = result.data.id;
for (const game of week) {
game.teamA.data.forEach(player => {
player.push(game.teamA.name);
});
game.teamB.data.forEach(player => {
player.push(game.teamB.name);
});
// Can't have sheet names containing : if we want to use .append()
let sheetName = game.time.toString().replace(':', '.').trim();
// add each game to weeks spreadsheet
await sheets.spreadsheets.batchUpdate ({
spreadsheetId : spreadsheetId,
resource: {requests: [
{addSheet: {properties: {title: sheetName }}}
]}
});
console.log("Spreadsheet Id:");
console.log(spreadsheetId);
// console.log(sheetName);
// let sheetId = await getSheetId(spreadsheetId,sheetName);
// // format cells
// let formatResources = {
// spreadsheetId: sheetId,
// resource: {
// requests: [
// {
// repeatCell: {
// range: {
// sheetId: sheetId,
// startRowIndex: 0,
// },
// cell: {
// userEnteredFormat: {
// textFormat: {
// bold: true,
// },
// },
// },
// fields: "userEnteredFormat.textFormat",
// },
// },
// ],
// },
// };
//await sheets.spreadsheets.batchUpdate(formatResources);
// add data to each game sheet
let resources = {
spreadsheetId: spreadsheetId,
resource:{
valueInputOption: "RAW",
data:[
{
range: "'" + sheetName + "'!A1:I1",
values: [['First','Last','Email','Position','Number','Team','Goals','Assists','Penalties']]
},
{
range: "'" + sheetName + "'!K1:L1",
values: [['Team','Shots']]
},
{
range: "'" + sheetName + "'!K2:L3",
values: [[game.teamA.data.name,''],[game.teamB.data.name,'']]
},
{
range: "'" + sheetName + "'!A2:F12",
values: game.teamA.data
},
{
range: "'" + sheetName + "'!A14:F24",
values: game.teamB.data
}
]
}
};
await sheets.spreadsheets.values.batchUpdate(resources);
}
// delete "Sheet1" (gid=0) from every spreadsheet
await sheets.spreadsheets.batchUpdate({
spreadsheetId: spreadsheetId,
resource: { requests: [
{deleteSheet : {sheetId :0}}
]}
});
}
};
Note: Google Sheets API/Service Details shows that I am not using that many write requests by the way:
Note 2: And I have had my quota limit increased:
Question 1: Am I correctly using batchUpdates, or am I missing a concept that can streamline this code further?
Question 2: My calculations are that I am using 245 write calls to the sheets api, but the "API/Service Details" console is showing 31. Am I missing some concept or miscalculating this somehow? How is my quota being exceeded here? Are the nested 5 arrays a single batchUpdate or 5 batchUpdates? This would add hundreds of write calls to the sheets api if the latter applies.
API Write Calculations:
Create 105 sheets (35 weeks * 3 games)
+105 write calls
Add 5 data ranges to each of the 105 sheets
+105 write calls (or is this 105 * 5 ?)
Delete "Sheet1" from all 35 sheets using:
+35 write calls
Edit: Per request, this is what a week looks like (I cut the length of 'data' , which is normally 10 people for readability. I'd also like to note that I am not having errors with writing these ranges if I choose 1 or 2 weeks instead of 35.
week:
[
{
teamA: {
data: [
[ 'Robert', 'Manning', 'robert.manning#email.com', 'C', '45' ],
[ 'Adrian', 'Martin', 'adrian.martin#email.com', 'RW', '5' ],
],
name: 'Green'
},
teamB: {
data: [
[ 'Isaac', 'Payne', 'isaac.payne#email.com', 'C', '11' ],
[ 'Alan', 'Lewis', 'alan.lewis#email.com', 'RW', '13' ],
],
name: 'Orange'
},
time: '4:30'
},
{
teamA: {
data: [
[ 'Stewart', 'Taylor', 'stewart.taylor#email.com', 'RW', '56' ],
[ 'Lucas', 'Davies', 'lucas.davies#email.com', 'RW', '85' ],
],
name: 'Yellow'
},
teamB: {
data: [
[ 'Dylan', 'Baker', 'dylan.baker#email.com', 'C', '11' ],
[ 'Edward', 'Dowd', 'edward.dowd#email.com', 'D', '65' ],
],
name: 'Black'
},
time: '6:00'
},
{
teamA: {
data: [
[ 'Gavin', 'Knox', 'gavin.knox#email.com', 'C', '45' ],
[ 'Paul', 'Wallace', 'paul.wallace#email.com', 'RW', '5' ],
],
name: 'Red'
},
teamB: {
data: [
['Andrew','Sanderson','andrew.sanderson#email.com','C','11'],
['Stewart','MacLeod','stewart.macleod#email.com','RW','13'],
],
name: 'Teal'
},
time: '7:30'
}
]
Edit 2:
I took bits of code out to restrict the scope of the question to API calls, so things like sheetName definitions were omitted, but here it is:
I believe your goal is as follows.
You want to reduce the number of requests of Sheets API by modifying your script.
In this case, how about the following modification?
In this modification, 2 API calls are used in the loop of for (const week of req.body.schedule) {,,,}.
Modified script:
for (const week of req.body.schedule) {
i++;
let fileMetadata = {
name: "Week-" + i,
mimeType: "application/vnd.google-apps.spreadsheet",
parents: [seasonResultsFolderId],
};
let result = await drive.files.create({auth: jwtClient, resource: fileMetadata});
let sheetId = result.data.id;
const sheetNames = week.map((game) =>
game.time.toString().replace(":", ".").trim()
);
// Add sheets and delete 1st tab.
await sheets.spreadsheets.batchUpdate({
spreadsheetId: sheetId,
resource: {
requests: [
...sheetNames.map((title) => ({
addSheet: {
properties: { title },
},
})),
{ deleteSheet: { sheetId: 0 } },
],
},
});
// Put values to each sheet.
const data = week.map((game, i) => {
game.teamA.data.forEach((player) => {
player.push(game.teamA.name);
});
game.teamB.data.forEach((player) => {
player.push(game.teamB.name);
});
return [
{
range: "'" + sheetNames[i] + "'!A1:I1",
values: [
[
"First",
"Last",
"Email",
"Position",
"Number",
"Team",
"Goals",
"Assists",
"Penalties",
],
],
},
{
range: "'" + sheetNames[i] + "'!K1:L1",
values: [["Team", "Shots"]],
},
{
range: "'" + sheetNames[i] + "'!K2:L3",
values: [
[game.teamA.name, ""],
[game.teamB.name, ""],
],
},
{
range: "'" + sheetNames[i] + "'!A2:F12",
values: game.teamA.data,
},
{
range: "'" + sheetNames[i] + "'!A14:F24",
values: game.teamB.data,
},
];
});
await sheets.spreadsheets.values.batchUpdate({
spreadsheetId: sheetId,
resource: { valueInputOption: "RAW", data },
});
}
In your situation, at Method: spreadsheets.batchUpdate, all requests for adding sheets and deleting a sheet can be included in one request.
In your situation, at Method: spreadsheets.values.batchUpdate, all requests for putting values to each sheet can be included in one request.
Note:
When Sheets API is used in a loop, an error might occur because of the continuous requests. When this script is run, such error occurs, please put the script for waiting in the loop.
If you want to reduce the number of requests of Drive API, the batch requests might be able to be used. Ref
References:
Method: spreadsheets.batchUpdate
Method: spreadsheets.values.batchUpdate

Apollo Server Resolver not returning all data (returned data is not complete)

My setup: Apollo server with express.js
MongoDB with Mongoose
My problem: When I run a query, my resolver is not fetching all of the data, just part of it.
Here is my resolver code:
getMarsContentForScreen: async (_, { screen, token }, context) => {
if (!context.screen) return {};
console.log(screen, token);
const contentOut = {};
const screenExist = await MarsScreen.findOne({
name: screen,
token: token,
});
if (screenExist) {
const content = await MarsContent.findOne({
screens: { $in: screenExist.id },
});
if (content) {
// ID
contentOut.id = content.id;
// NAME
contentOut.name = content.name;
// ENTRY
contentOut.entry = [{ entryVideos: [] }];
content.entry.map(async (val) => {
let file = await Asset.findById(val, 'uri');
if (file && file.uri) {
contentOut.entry[0].entryVideos.push(
file.uri.split('/').slice(-1)[0]
);
}
});
// EQUIPMENT
contentOut.equipment = [];
content.equipment.map(async (val) => {
let equipment = await MarsEquipment.findById(
val.id,
'name thumbnail background'
);
if (equipment) {
contentOut.equipment.push({
id: val.id,
name: equipment.name,
panelImage: equipment.thumbnail.split('/').slice(-1)[0],
productImage: equipment.background.split('/').slice(-1)[0],
});
}
});
// EXERCISES
contentOut.exercises = [];
content.exercises.map(async (val, index) => {
contentOut.exercises.push({
equipment: val.equipment,
content: [],
});
val.content.map(async (valC) => {
let exercise = await MarsExercise.findById(
valC.id,
'name level text thumbnail video'
);
if (exercise) {
let instructions = [];
for (const [key, value] of Object.entries(
JSON.parse(exercise.text)
)) {
instructions.push(value);
}
contentOut.exercises[index].content.push({
id: valC.id,
position: valC.position,
name: exercise.name,
level: exercise.level,
instructions: instructions,
panelImage: exercise.thumbnail.split('/').slice(-1)[0],
programVideo: exercise.video.split('/').slice(-1)[0],
});
}
});
});
// OPTIONS
contentOut.options = [];
let bgImage = await Asset.findById(content.options[0].bgImage, 'uri');
bgImage = bgImage.uri.split('/').slice(-1)[0];
contentOut.options = [
{
bgImage: bgImage,
cards: [],
},
];
content.options[0].cards.map(async (val, index) => {
let cardImg = await Asset.findById(val.panelImage, 'uri');
if (cardImg) {
contentOut.options[0].cards.push({
name: val.name,
panelImage: cardImg.uri.split('/').slice(-1)[0],
subheading: val.subheading,
action: val.action,
});
if (val.overlay) {
contentOut.options[0].cards[index].overlay = val.overlay;
}
if (
val.externalApp &&
val.externalApp.appName &&
val.externalApp.playStoreId
) {
contentOut.options[0].cards[index].externalApp = {
appName: val.externalApp.appName,
playStoreId: val.externalApp.playStoreId,
};
}
}
});
// WORKOUTS
contentOut.workouts = [];
content.workouts.map(async (val) => {
let workout = await MarsWorkout.findById(
val.id,
'name thumbnail video text required'
);
if (workout) {
contentOut.workouts.push({
id: val.id,
position: val.position,
name: workout.name,
panelImage: workout.thumbnail.split('/').slice(-1)[0],
programVideo: workout.video.split('/').slice(-1)[0],
instructions: workout.text,
required: workout.required,
});
}
});
// FILES
contentOut.files = [];
content.files.map(async (val) => {
let file = await Asset.findById(val, 'uri updated_at');
if (file) {
contentOut.files.push({
id: val,
uri: file.uri,
filename: file.uri.split('/').slice(-1)[0],
timestamp: file.updated_at,
});
}
});
return contentOut;
} else {
return {};
}
}
}
Here is the query I'm running in the Playground:
query {
getMarsContentForScreen(screen: "GS123123123123", token: "token-here") {
id
name
entry {
entryVideos
}
equipment {
id
name
position
panelImage
productImage
}
exercises {
equipment
content {
id
position
name
level
panelImage
programVideo
instructions
}
}
options {
bgImage
cards {
name
panelImage
subheading
action
overlay
externalApp {
appName
playStoreId
}
}
}
workouts {
id
position
name
panelImage
programVideo
required
instructions
}
files {
id
filename
uri
timestamp
}
}
}
And here is the output of what I'm getting:
{
"data": {
"getMarsContentForScreen": {
"id": "6203d63f54a0bd82832288c5",
"name": "sdfgsdfg",
"entry": [
{
"entryVideos": [
"6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"9b1628af-e69e-4d0e-9d53-b472a963a1ec.mp4",
"830b0258-70f1-4206-b07b-fb60508e33c5.mp4"
]
}
],
"equipment": [
{
"id": "62025aa4237005069c569d63",
"name": "dsfgsdfg",
"position": null,
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"productImage": "da245241-335e-4021-929c-d177a851c2ea.jpg"
},
{
"id": "62025afa237005069c569d99",
"name": "sdfgsdfgsdfgsdfgsdfgsdfgweqqwerwr",
"position": null,
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"productImage": "da245241-335e-4021-929c-d177a851c2ea.jpg"
},
{
"id": "62025af4237005069c569d92",
"name": "sdfgsdfgsdfgdsf",
"position": null,
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"productImage": "da245241-335e-4021-929c-d177a851c2ea.jpg"
}
],
"exercises": [
{
"equipment": "dsfgsdfg",
"content": [
{
"id": "62025b27237005069c569dc0",
"position": 1,
"name": "sdfgsdfg",
"level": "Intermediate",
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"instructions": [
"sdfgsdfg",
"sdfgsdfg",
"sdfg"
]
},
{
"id": "62025b30237005069c569dc7",
"position": 2,
"name": "sdfgsdfgsdfg",
"level": "Intermediate",
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"instructions": [
"sdfgsdfg",
"sdfg",
"hgfjgh"
]
}
]
},
{
"equipment": "sdfgsdfgsdfgdsf",
"content": [
{
"id": "62025b80237005069c569e13",
"position": 1,
"name": "sdfg",
"level": "Intermediate",
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"instructions": [
"sdfg",
"sdfgsdfg",
"sdfgdf"
]
}
]
},
{
"equipment": "sdfgsdfgsdfgsdfgsdfgsdfgweqqwerwr",
"content": [
{
"id": "62025b88237005069c569e1a",
"position": 1,
"name": "uitytyui",
"level": "Intermediate",
"panelImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"programVideo": "6bb847e5-8b9a-477b-bfd1-68a109b3c707.mp4",
"instructions": [
"ytuityui",
"tyui",
"tyuityuityui"
]
}
]
}
],
"options": [
{
"bgImage": "da245241-335e-4021-929c-d177a851c2ea.jpg",
"cards": []
}
],
"workouts": [],
"files": []
}
}
}
As you can see, everything from "options" : [{"cards"}] is empty, but it shouldn't be, as there is the data in the database for it.
What is even more interesting, is that when I console.log the contentOut object inside the last .map function (content.files.map()) I'm getting the full response.
Basically it looks like my resolver is returning the content before all of it is gathered.
If I add some if statement to check if all of my content is in the contentOut object, I'm getting empty response, just like the resolver couldn't be bothered to wait for all of the content...
Any ideas?
Many thanks in advance!
Ok, so after more Googling and fighting with it, I've re-write the whole code and use Promise.all for each part of the function in order to make sure that it will wait for the outcome of each await, before returning the value.
Now the code looks like this:
getMarsContentForScreen: async (_, { screen, token }, context) => {
if (!context.screen) return {};
console.log(screen, token);
const contentOut = {};
const screenExist = await MarsScreen.findOne({
name: screen,
token: token,
});
const getEntryVideos = async (content) => {
let result = [{ entryVideos: [] }];
await Asset.find({ _id: { $in: content } }, 'uri').then((response) =>
response.map((val) => {
result[0].entryVideos.push(val.uri.split('/').slice(-1)[0]);
})
);
return result;
};
const getEquipment = async (content) => {
let result = [];
const ids = content.map((val) => {
return val.id;
});
await MarsEquipment.find(
{ _id: { $in: ids } },
'id name thumbnail background'
).then((response) =>
response.map((val) => {
result.push({
id: val.id,
name: val.name,
panelImage: val.thumbnail.split('/').slice(-1)[0],
productImage: val.background.split('/').slice(-1)[0],
});
})
);
return result;
};
const getExercises = async (content) => {
let result = [];
const ids = [].concat(
...content.map((val) => {
result.push({
equipment: val.equipment,
content: [],
});
return val.content.map((valC) => {
return valC.id;
});
})
);
await MarsExercise.find(
{ _id: { $in: ids } },
'id name level text thumbnail video product'
).then((response) =>
response.map((exer) => {
let instructions = [];
const index = result.indexOf(
result.find((equip) => equip.equipment === exer.product)
);
for (const [key, value] of Object.entries(JSON.parse(exer.text))) {
instructions.push(value);
}
result[index].content.push({
id: exer.id,
position: exer.position,
name: exer.name,
level: exer.level,
instructions: instructions,
panelImage: exer.thumbnail.split('/').slice(-1)[0],
programVideo: exer.video.split('/').slice(-1)[0],
});
})
);
return result;
};
const getOptions = async (content) => {
let result = content;
const ids = content[0].cards.map((val) => {
return val.panelImage;
});
await Asset.findById(content[0].bgImage, 'uri').then((response) => {
result[0].bgImage = response.uri.split('/').slice(-1)[0];
});
await Asset.find({ _id: { $in: ids } }, 'id uri').then((response) =>
response.map((val) => {
let index = result[0].cards.indexOf(
result[0].cards.find((card) => card.panelImage === val.id)
);
result[0].cards[index].panelImage = val.uri.split('/').slice(-1)[0];
})
);
return result;
};
const getWorkouts = async (content) => {
let result = content;
const ids = content.map((val) => {
return val.id;
});
await MarsWorkout.find(
{ _id: { $in: ids } },
'id name thumbnail video text required'
).then((response) => {
response.map((val) => {
let index = result.indexOf(
result.find((work) => work.id === val.id)
);
result[index].panelImage = val.thumbnail.split('/').slice(-1)[0];
result[index].programVideo = val.video.split('/').slice(-1)[0];
});
});
return result;
};
const getFiles = async (content) => {
let result = [];
await Asset.find({ _id: { $in: content } }, 'id uri updated_at').then(
(response) => {
response.map((val) => {
result.push({
id: val.id,
uri: val.uri,
filename: val.uri.split('/').slice(-1)[0],
timestamp: val.updated_at,
});
});
}
);
return result;
};
if (screenExist) {
const content = await MarsContent.findOne({
screens: { $in: screenExist.id },
});
if (content) {
// ID
contentOut.id = content.id;
// NAME
contentOut.name = content.name;
// ENTRY
const entry = getEntryVideos(content.entry);
// EQUIPMENT
const equipment = getEquipment(content.equipment);
// EXERCISES
const exercises = getExercises(content.exercises);
// OPTIONS
const options = getOptions(content.options);
// WORKOUTS
const workouts = getWorkouts(content.workouts);
// FILES
const files = getFiles(content.files);
// PROMISE
const results = await Promise.all([
entry,
equipment,
exercises,
options,
workouts,
files,
]);
//console.log(results);
return {
id: content.id,
name: content.name,
entry: results[0],
equipment: results[1],
exercises: results[2],
options: results[3],
workouts: results[4],
files: results[5],
};
} else {
return {};
}
}
},

Mongoose / Node : How to add an object to an array?

I have a problem when I try to update an array with Mongoose/Node.
I want to add my new price for example, req.body value is : { price: 12 } or req.body is : { description: 'my description' } but when I do this the total array is replace by just my new object ! :/
Here is my model:
const restaurantSchema = mongoose.Schema({
userId: { type: Object, required: true },
name: { type: String },
menus: [{
name: { type: String },
price: { type: String },
description: { type: String },
}],
})
And my node Js code :
const menuUpdate = req.body;
const menuId = req.params.menuId;
const userId = userIdFromToken(req);
const filter = {
userId: userId,
"menus._id": menuId
};
const update = { $set: { "menus.$": menuUpdate } };
const options = {
upsert: true,
new: true
};
Restaurant.findOneAndUpdate(filter, update, options).then(() => {
return res.status(204).json({ message: "Menus updated " });
});
Thanks for your help,
David
====
I change my code with the help of #aks, like this...
const menuUpdate = req.body;
for (const [key, value] of Object.entries(menuUpdate)) {
this.menuKey = `${key}`;
this.menuValue = `${value}`;
}
if (this.menuKey === 'name') {
this.update = { $set: { "menus.$.name": this.menuValue } };
}
if (this.menuKey === 'price') {
this.update = { $set: { "menus.$.price": this.menuValue } };
}
if (this.menuKey === 'description') {
this.update = { $set: { "menus.$.description": this.menuValue } };
}
const menuId = req.params.menuId;
const userId = userIdFromToken(req);
const filter = {
userId: userId,
'menus._id': menuId,
};
const options = {
upsert: true,
new: true
};
Restaurant
.findOneAndUpdate(
filter,
this.update,
options,
)
.then ( () => {
return res.status(204).json({ message: 'Menus updated ' });
});
Is there a way to simplify that without if ?
Your Node code
menus: [{
name: "toto",
price: 25,
description: "custom sweet",
}]
Now You have to update only the price from 25 to 45 for that you have to send the whole array.
So you have to simple set the array with this value
And if you go to other approach
then on req.body add one more parameter i.e menuIndex: 2
And on you update request make the condition if menuIndex is 2 then update specific column
const menuUpdate = req.body;
const menuId = req.params.menuId;
const userId = userIdFromToken(req);
const filter = {
userId: userId,
"menus._id": menuId
};
let update = {};
if (req.body.menuIndex === 1) {
update = { $set: { "menus.$.name": req,body.val} };
}
if (req.body.menuIndex === 2) {
update = { $set: { "menus.$.price": req,body.val
} };
}
if (req.body.menuIndex === 3) {
update = { $set: { "menus.$.description": req,body.val} };
}
const options = {
upsert: true,
new: true
};
Restaurant.findOneAndUpdate(filter, update, options).then(() => {
return res.status(204).json({ message: "Menus updated " });
});

Mongoose, MATCH operator doesn't return results

The company parameter has a valid value to obtain the result, but I get an empty array.
company = company.replace('.', '');
company = company.replace('.', '');
company = company.replace('-', '');
company = company.replace('/', '');
try {
const result = await cteModel.aggregate([
{
$addFields': {
date': {
$dateFromString': { 'dateString': '$dataEmissao' }
}
}
},
{
$match: { 'empresa': company }
},
{
$sort: { date: -1, _id: 1 }
}]);
res.status(200).send(result);
} catch (error) {
console.log(error);
return res.status(500).json(error);
}
The query return the deisered result if I substitute the variable by string, like this : { $match: { 'empresa': '1111' } },

Payload Syntax for batchUpdate

I am using the googleapis module in a Node application. I was using version 21 but have just updated to version 52 due to a security vulnerability in the older version.
There are several breaking changes. I have overcome most except for formatting a date/time string. Is the following payload correct for formatting a date/time value in cell A11?
const formatDate = (next) => {
sheets.spreadsheets.batchUpdate({
auth: authClient,
spreadsheetId: sheetId,
requestBody: {
requests: [{
"repeatCell": {
range: { sheetId: 0, startRowIndex: 10, endRowIndex: 11, startColumnIndex: 0, endColumnIndex: 1},
cell: { userEnteredFormat: { numberFormat: { "type": "DATE_TIME", "pattern": "ddd yyyy-mm-dd hh:mm" } } },
fields: "userEnteredFormat.numberFormat"
}
}]
}
}, (err, response) => {
// ...
next();
}
);
}
No errors were returned with the above payload, but the formatting is not taking place. Is the key requestBody? Previously I was using resource.
I used async to perform authentication before formatting the date:
const authClient = new google.auth.JWT(client_email, null, private_key, SCOPES, null);
const sheetId = "1vgiEnV8fU_MrnIy31fbPAzhHz.......";
function authenticate(next) {
authClient.authorize((err) => {
next(err);
}
}
const tasks = [ authenticate, insertRow, formatdate ];
require("async").series(tasks);
Code for insertRow is not included here, but that works without problem.
I think that your script is correct. The cell format of "A11" is modified with the request body. And in this case, the request body can be used for both requestBody and resource.
But please confirm the following points, again.
sheetId of spreadsheetId: sheetId, is required to be the Spreadsheet ID.
In this case, when =now() is put to the cell "A11" and run the script, you can see the modified cell format.
By the following modification, you can check the returned values from Sheets API.
sheets.spreadsheets.batchUpdate(
{
auth: authClient,
spreadsheetId: "spreadsheetId", // <--- Please check this.
requestBody: {
requests: [
{
repeatCell: {
range: {
sheetId: 0,
startRowIndex: 10,
endRowIndex: 11,
startColumnIndex: 0,
endColumnIndex: 1,
},
cell: {
userEnteredFormat: {
numberFormat: {
type: "DATE_TIME",
pattern: "ddd yyyy-mm-dd hh:mm",
},
},
},
fields: "userEnteredFormat.numberFormat",
},
},
],
},
},
(err, res) => {
if (err) {
console.log(err);
return;
}
console.log(res.data);
}
);
Note:
In my environment, I tested your script with googleapis#52.1.0, and I could confirm the script worked.
This modified script supposes that your authorization process for using Sheets API has already been done.
References:
googleapis for Node.js
Method: spreadsheets.batchUpdate
RepeatCellRequest
Added:
Sample script for testing:
const client_email = "###"; // Please set here.
const private_key = "###"; // Please set here.
const spreadsheetId = "###"; // Please set here.
const { google } = require("googleapis");
let jwtClient = new google.auth.JWT(
client_email,
null,
private_key,
["https://www.googleapis.com/auth/spreadsheets"]
);
jwtClient.authorize((err) => {
if (err) console.log(err);
});
const sheets = google.sheets({ version: "v4", auth: jwtClient });
sheets.spreadsheets.batchUpdate(
{
spreadsheetId: spreadsheetId,
requestBody: {
requests: [
{
repeatCell: {
range: {
sheetId: 0,
startRowIndex: 10,
endRowIndex: 11,
startColumnIndex: 0,
endColumnIndex: 1,
},
cell: {
userEnteredFormat: {
numberFormat: {
type: "DATE_TIME",
pattern: "ddd yyyy-mm-dd hh:mm",
},
},
},
fields: "userEnteredFormat.numberFormat",
},
},
],
},
},
(err, res) => {
if (err) {
console.log(err);
return;
}
console.log(res.data);
}
);

Resources