Google Sheets API with NodeJS, Json error - node.js

I've been playing with the google sheets API in NodeJS for the past couple of days and I'm having some trouble. I took one of the examples from their docs and edited it to suit my needs, however at one point it started returning an error. The console logs:
The API returned an error: SyntaxError: Unexpected token  in JSON at position 0
Here's my code:
function getApplies(auth) {
const sheets = google.sheets({version: 'v4', auth});
sheets.spreadsheets.values.get({
spreadsheetId: '1g5kYIIQy5f-UTSmOatTBPOto13ccfPvsJKQpxT6lhjE',
range: 'A2:I',
}, (err2, res) => {
if (err2) return console.log('The API returned an error: ' + err2);
const rows = res.data.values;
if (rows.length) {
//saving data here...
}
});
}
Any idea what I could be doing wrong here? Thanks!

From this link:
Go to Google Console
Create a new Credential of type service account key
Download your json file
npm install google-spreadsheet#2.0.3
var GoogleSpreadsheet = require('google-spreadsheet');
var creds = require('./client_secret.json');
var doc = new GoogleSpreadsheet('*spreadsheet ID*');
doc.useServiceAccountAuth(creds, function (err) {
doc.getRows(1, function (err, rows) {
if (err) {
console.log(err);
} else {
console.log(rows.length);
console.log(rows);
for (var i=0; i < rows.length; i++) {
console.log(rows[i].TITLE-NAME-OF-YOUR-FIRST-COLUMN, rows[i].TITLE-NAME-OF-YOUR-SECOND-COLUMN);
}
}
});
});
Replace the spreadsheet ID with the ID found in the spreadsheet URL.
Share the sheet with the service account email. It is in your json file.
Hope this helps!!

For v4 of googleapi use:
const { google } = require("googleapis");
const key = require("service_account_credentials.json");
const client = new google.auth.JWT(key.client_email, null, key.private_key, [
"https://www.googleapis.com/auth/spreadsheets",
]);
client.authorize(function (err, tokens) {
const gsapi = google.sheets({ version: "v4", auth: client });
const opt = {
spreadsheetId: "spreadsheetId",
range: "Sheet1!A:T",
};
gsapi.spreadsheets.values.get(opt).then(res => console.log(res));
});

You need to parse your json file like this, first:
const rows = JSON.parse(res.data.values);

Related

SendGrid event export to GCP

Basically, I'm trying to export the SendGrid event data to GCP storage using a cloud function in Node.js 16 using SendGrid's event Webhook functionality
I'm currently running (with example naming conventions);
const {BigQuery} = require('#google-cloud/bigquery');
const projectId = 'my_project';
const dataset = 'my_dataset';
const tableName = 'sendgrid_events';
//Initialize bigquery object
const bigQuery = new BigQuery({
projectId: projectId
});
exports.helloWorld = (req, res) => {
let message = req.query.message || req.body.message || 'Hello World!';
var timestamp = new Date().getTime()+'';
var data = req.body;
var rows = [];
for(var i=0; i< data.length; i++){
rows.push({
'hit_timestamp' : timestamp,
'email': data[i]['email'],
'event': data[i]['event'],
'category': data[i]['category']+ "",
'event_id': data[i]['sg_event_id'],
'event_payload' : JSON.stringify(data[i]),
});
}
send_to_bq(rows);
res.status(200).send(message);
};
function send_to_bq(rows){bigQuery
.dataset(dataset)
.table(tableName)
.insert(rows)
.then((data) => {
console.log(`Inserted rows : `, rows.length);
//console.log(data);
})
.catch(err => {
console.error('BQ ERROR:', err);
console.error('BQ ERROR:', err.response.insertErrors[1].error);
});
}
However, the data doesn't seem to pass into the table.
Any help is appreciated.
The solution is authentication, when deploying the cloud function setting it as unauthenticated means this boiler plate code works. However, if you require authentication then the permissions variable need to be added so google cloud can verify the data is being passed from the correct place.

Firebase db.ref is not a function (node.js)

Can someone please tell me what is wrong in my code before I go back to MongoDB?
Project is in Node.js (Next.js)
This is how I set firebase (it works for authentication with Google Login for instance):
import { initializeApp } from 'firebase/app';
const credentials = {
...
}
const firebase = initializeApp(credentials);
export default firebase;
then this is my api js file where it throws error "db.ref" is not a function:
import firebase from '#/firebase/firebase'
import { getDatabase, ref, onValue, update, child, orderByChild, equalTo, once } from "firebase/database"
export default async (req, res) => {
const db = getDatabase(firebase);
if (req.method === 'POST') {
const body = req.body
const playlistTracks = body.playlist
const playlistName = body.name
const uid = body.uid
const data = ...
console.log(data)
var ref = db.ref().child('users');
ref.child(uid).orderByChild('n').equalTo(playlistName).once("child_added", function(snapshot) {
let listId = snapshot.key;
db.ref("users/" + uid + "/" + listId).update(data);
res.send({ risp : 'ok' })
});
}
}
realtime database structure is:
- users
- <user uid>
- <playlist uid>
c: []
n: "playlist name"
so I'm trying to first retrieve the correct playlist by it's name ("n" value) comparing all "n" with the name of the given playlist, then I'd need to update (overwrite) it with my object (data)
UPDATE:
So I found the other methods Web version 9 (modular) in the documentation, as suggested by Frank van Puffelen below, but it now thorws a error
#firebase/database: FIREBASE WARNING: Exception was thrown by user
callback. Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they
are sent to the client
My code now is like this:
try {
const myQuery = query(ref(db, 'users/' + uid), orderByChild('n'), equalTo(playlistName));
onChildAdded(myQuery, (data) => {
let listId = data.key;
const updates = {};
updates["users/" + uid + "/" + listId] = dataToUpdate;
update(ref(db), updates);
}, {
onlyOnce: true
});
res.send({ risp : 'ok' })
} catch (e) {
res.status(400).end();
}
also tried like this, but it's the same error:
const myQuery = query(ref(db, 'users/' + uid), orderByChild('n'), equalTo(playlistName));
onChildAdded(myQuery, (data) => {
let listId = data.key;
update(ref(db, "users/" + uid + "/" + listId), dataToUpdate)
.then(() => {
res.send({ risp: 'ok' })
})
.catch((error) => {
res.status(400).end();
});
}, {
onlyOnce: true
});
You're using the new modular API, so can't use namespaced accessors like db.ref() anymore. Instead use ref(db, 'users').
I highly recommend keeping the documentation handy while upgrading this code to the new API version, as it has example of the modular and namespaced API side by side. The upgrade guide is probably also a worthwhile read).

Why getting this "Authentication_MissingOrMalformed" error during Microsoft Graph API call?

const msRestAzure = require('ms-rest-azure');
const { GraphRbacManagementClient } = require('azure-graph');
module.exports = async function (context, req) {
try{
const credentials = await msRestAzure.loginWithServicePrincipalSecret(clientId, clientSecret, tanent);
const client = new GraphRbacManagementClient(credentials, tenantId);
const results = await client.users.list();
context.res = {
body: results
};
} catch (error) {
console.log('error==> ',error); // Getting error: Authentication_MissingOrMalformed
context.res = {
body: error
};
}
}
I want to get all users list using azure graph sdk. But after calling the client.users.list() function I'm getting the error ("Authentication_MissingOrMalformed"). How do I fix this error and get all users list.
How to get all users list from Azure Active Directory using Azure Graph SDK (Nodejs) ?
The main problem is missing { tokenAudience: 'graph' }, please refer to my code:
const msRestAzure = require('ms-rest-azure');
const { GraphRbacManagementClient } = require('azure-graph');
module.exports = async function (context, req) {
try{
msRestAzure.loginWithServicePrincipalSecret("clientId", "clientSecret", "tenantId", { tokenAudience: 'graph' }, function (err, credentials) {
if (err) return console.log(err);
const client = new GraphRbacManagementClient(credentials, "tenantId");
client.users.list((err, results, request, response) => {
if (err) return console.log(err);
console.log(JSON.parse(response.body).value.length);
});
});
} catch (error) {
console.log('error==> ',error);
context.res = {
body: error
};
}
}
After running the code above, if the number of users in your AD is greater than 100, it will output 100 because graph api can response 100 users in a page(default is 100).
==================================Update================================
Please check if you have added the permission to the application registered in Azure AD. If you didn't add the permission, please follow the below steps:
1. Go to the application which registered in your Azure AD (It's the application which you use its clientId).
2. Add the permission.
3. Click "Grant admin consent for xxx".
4. After a few minutes, run your code again.

Having 2 auth clients results in 404 on calendar event creation

I'm trying to create 2 calendar events across 2 calendars using the Google Node.js API client library in a single function. I'm using 2 separate auth objects that I'm retrieving like so:
var auth1 = await google.auth.getClient({ credentials: config.account1Creds, scopes: ["https://www.googleapis.com/auth/calendar.events"] });
var auth2 = await google.auth.getClient({ credentials: config.account2Creds, scopes: ["https://www.googleapis.com/auth/calendar.events"] });
I can create the event on the first calendar fine, but when I create the event on the 2nd calendar, I get a 404 Not Found back from the Google Calendar API server.
If I comment out the first var auth1 = await google.auth... line and only create the event on the 2nd calendar, everything is fine and the event is created successfully.
It kinda feels as if the first time getClient is called, it sets a global auth object that gets used for all remaining API requests and it can't be replaced, but thats just a hunch I have and I don't actually know.
Anybody have any ideas why this is happening?
EDIT:
GetGoogleCalendarService: async function(credentials)
{
var auth = await google.auth.getClient({ credentials: credentials, scopes: ["https://www.googleapis.com/auth/calendar.events"] });
return google.calendar({ version: "v3", auth: auth });
},
InsertCalendarEvent: function(calendar, entry, callback)
{
calendar.events.insert(entry, callback);
},
SendInvitesToEvent: async function (request, response)
{
//build the calendar event
var entry = {
...
}
//insert into operations calendar
var opsCal = await Events.GetGoogleCalendarService(config.GetConfig().OpsCalendarCredentials);
Events.InsertCalendarEvent(mainCal, entry);
//insert into public calendar
var publicCal = await Events.GetGoogleCalendarService(config.GetConfig().PublicCalendarCredentials);
Events.InsertCalendarEvent(publicCal, entry, async function(err, event)
{
//err: 404 Not Found
...
}
}
You want to insert events to 2 calendars using 2 clients.
Client "A" inserts an event to Calendar "A".
Client "B" inserts an event to Calendar "B".
You are using googleapis of Node.js.
If my understanding is correct, how about this modification? In this modification, I separated retrieving auth by each credential.
Modified script:
const { google } = require("googleapis");
function insertEvent(calendar, calendarId) {
// insert event
}
async function getService(c) {
var auth = await google.auth.getClient({
credentials: c,
scopes: ["https://www.googleapis.com/auth/calendar.events"]
});
return google.calendar({ version: "v3", auth: auth });
}
function main() {
getService(config.account1Creds).then(calendar => {
insertEvent(calendar, "### calendarId ###");
});
getService(config.account2Creds).then(calendar => {
insertEvent(calendar, "### calendarId ###");
});
}
main();
Note:
This is a sample script. So please modify this for your situation.
In my environment, I could confirm that this script works. But if this didn't work and this modification was not the result you want, I apologize.
Edit:
From your current script, I modified as follows.
Is opsCal mainCal? In my modification, opsCal is used as mainCal.
Modified script:
From:
//insert into operations calendar
var opsCal = await Events.GetGoogleCalendarService(config.GetConfig().OpsCalendarCredentials);
Events.InsertCalendarEvent(mainCal, entry);
//insert into public calendar
var publicCal = await Events.GetGoogleCalendarService(config.GetConfig().PublicCalendarCredentials);
Events.InsertCalendarEvent(publicCal, entry, async function(err, event)
{
//err: 404 Not Found
...
}
To:
Events.GetGoogleCalendarService(config.GetConfig().OpsCalendarCredentials).then(
opsCal => {
Events.InsertCalendarEvent(opsCal, entry);
}
);
Events.GetGoogleCalendarService(config.GetConfig().PublicCalendarCredentials).then(
publicCal => {
Events.InsertCalendarEvent(publicCal, entry);
}
);

Check file exists before creating Google Drive API

I have put together a script that creates a file in a folder on Google Drive, when I make amendments to that file and push it back up it will create a new version (which is obviously correct). What I would like to do is check that the file exists first (by name if I can) and then update that file if exists or create new if it does not.
# index.js
const google = require('googleapis');
const fs = require('fs');
const config = require('./creds.json');
const drive = google.drive('v3');
const targetFolderId = "123456789"
const jwtClient = new google.auth.JWT(
config.client_email,
null,
config.private_key,
['https://www.googleapis.com/auth/drive'],
null
);
jwtClient.authorize((authErr) => {
if (authErr) {
console.log(authErr);
return;
}
const fileMetadata = {
name: './file.xlsx,
parents: [targetFolderId]
};
const media = {
mimeType: 'application/vnd.ms-excel',
body: fs.createReadStream('./file.xlsx' )
};
drive.files.create({
auth: jwtClient,
resource: fileMetadata,
media,
fields: 'id'
}, (err, file) => {
if (err) {
console.log(err);
return;
}
console.log('Uploaded File Id: ', file.data.id);
});
});
Update
As D Levine mentions I can use drive.files.list but I am having problems finding a file. I set up a service account so not sure if that makes a difference. My code is below but all I get at the moment is undefined returned. I would also like to search within specific folders but can't seem to find this option anywhere.
// Check File exists
drive.files.list({
auth: jwtClient,
spaces: 'drive',
q: "name='liverpool_away.xlsx'",
pageSize: 10,
fields: "nextPageToken, files(id, name)"
}, function(err, response) {
if (err) {
console.log('The API returned an error: ' + err);
return;
}
var files = response.files;
console.log("Files: " + files);
if (files.length == 0) {
console.log('No files found.');
} else {
console.log('Files:');
for (var i = 0; i < files.length; i++) {
var file = files[i];
console.log('%s (%s)', file.name, file.id);
}
}
});
You can find if a file exists by searching by name with driveService.files().list(). If you receive 1 result, then that is the file that you were looking for and you can get the file ID. If you receive 0 results, then the file does not exist.
A page describing how to search is here (with example code at the bottom): https://developers.google.com/drive/v3/web/search-parameters
The REST endpoint is documented here: https://developers.google.com/drive/v3/reference/files/list

Resources