How to update a Contentful entry using content-management node module - contentful

I am trying to update an existing entry in Contentful using the content-management node module.
Here is the code I am using, however I am not able to update the entry nor do I see any error.
var client = contentful.createClient({
// A valid access token for your user (see above on how to create a valid access token)
accessToken: 'accessToken value',
// Enable or disable SSL. Enabled by default.
secure: true
});
var log = console.log.bind(console);
client.getSpace('spaceId value').then(function(space) {
//return space.getEntries();
space.updateEntry('Sites', {
sys: { id: 'entryId value', version: 15 },
fields: {
siteName: { 'en-US': 'value' }
}
});
}).then(log, log);
res.send('hello');

When you update an entry you don't need to pass the content type id as the first argument. You should just pass the data object as the first argument (this will change on the next major version of contentful-management).

Related

Google cloud tasks NodeJS api: Get queue stats?

I want to obtain the stats field for a queue in google cloud tasks using the nodejs client library #google-cloud/tasks. The stats field only exists in the v2beta3 version, however to get it we need to pass a query params readMask=*, but I don't know how to pass it using the client lib.
I tried using the otherArgs params, but its not working.
const tasks = require('#google-cloud/tasks');
const client = new tasks.v2beta3.GoogleCloudTasks()
// Get queue containing stats
const queue = await client.getQueue({name: '..'}, {otherArgs: {readMask: '*'}})
The readMask specifies which paths of the response object to fetch. The response will include all possible paths with placeholders like null, UNSPECIFIED, etc. and then contain the actual values you want.
const request = {
...
readMask: { paths: ['name', 'stats', 'state', ...] }
};
getQueue
const { v2beta3 } = require('#google-cloud/tasks');
const tasksClient = new v2beta3.CloudTasksClient();
async function main() {
const request = {
name: 'projects/PROJECT/locations/LOCATION/queues/QUEUE',
readMask: { paths: ['name', 'stats'] }
};
const [response] = await tasksClient.getQueue(request);
console.log(response);
}
main();
/*
{
name: 'projects/PROJECT/locations/LOCATION/queues/QUEUE',
...
stats: {
tasksCount: '113',
oldestEstimatedArrivalTime: null,
executedLastMinuteCount: '0',
concurrentDispatchesCount: '0',
effectiveExecutionRate: 500
}
}
*/
listQueues
const { v2beta3 } = require('#google-cloud/tasks');
const tasksClient = new v2beta3.CloudTasksClient();
async function main() {
const request = {
parent: 'projects/PROJECT/locations/LOCATION',
readMask: { paths: ['name', 'stats'] }
};
const [response] = await tasksClient.listQueues(request);
console.log(response);
}
main();
/*
[
{
name: 'projects/PROJECT/locations/LOCATION/queues/QUEUE',
...
stats: {
tasksCount: '1771',
oldestEstimatedArrivalTime: [Object],
executedLastMinuteCount: '0',
concurrentDispatchesCount: '0',
effectiveExecutionRate: 500
}
},
...
]
*/
By taking a look at the source code for the client library I see no reference for the readMask parameter as specified on the v2beta3 version of the REST API projects.locations.queues.get method.
The relevant method on the NodeJS client library getQueue() expects a type of request IGetQueueRequest that doesn't have the readMask parameter and is only expecting the name property.
Nonetheless this implementation might change in the future to include a relevant method to get the stats.
Regarding the REST API itself, there is an error on the public docs on the readMask section as * is not a valid character. If you want to get the Queue.stats field you should simply enter stats on the readMask parameter. If you want to get all the relevant fields you should enter all of them (e.g. name,rateLimits,retryConfig,state,taskTtl,tombstoneTtl,type,stats should get all the relevant fields you get from calling the method + the Queue.stats field).
The following picture should help you.
As a workaround if you click on the expand symbol on the Try this API section of the docs for the relevant method you could click on the JAVASCRIPT section and get the relevant code as how to build the request as shown on the following picture.
EDIT JANUARY 23rd 2020
The documentation was corrected to inform that in order to express that:
[Queue.stats] will be returned only if it was explicitly specified in the mask.
Which translates that simply writing stats under the readMask field will return the stats.

How can one upload an image to a KeystoneJS GraphQL endpoint?

I'm using TinyMCE in a custom field for the KeystoneJS AdminUI, which is a React app. I'd like to upload images from the React front to the KeystoneJS GraphQL back. I can upload the images using a REST endpoint I added to the Keystone server -- passing TinyMCE an images_upload_handler callback -- but I'd like to take advantage of Keystone's already-built GraphQL endpoint for an Image list/type I've created.
I first tried to use the approach detailed in this article, using axios to upload the image
const getGQL = (theFile) => {
const query = gql`
mutation upload($file: Upload!) {
createImage(file: $file) {
id
file {
path
filename
}
}
}
`;
// The operation contains the mutation itself as "query"
// and the variables that are associated with the arguments
// The file variable is null because we can only pass text
// in operation variables
const operation = {
query,
variables: {
file: null
}
};
// This map is used to associate the file saved in the body
// of the request under "0" with the operation variable "variables.file"
const map = {
'0': ['variables.file']
};
// This is the body of the request
// the FormData constructor builds a multipart/form-data request body
// Here we add the operation, map, and file to upload
const body = new FormData();
body.append('operations', JSON.stringify(operation));
body.append('map', JSON.stringify(map));
body.append('0', theFile);
// Create the options of our POST request
const opts = {
method: 'post',
url: 'http://localhost:4545/admin/api',
body
};
// #ts-ignore
return axios(opts);
};
but I'm not sure what to pass as theFile -- TinyMCE's images_upload_handler, from which I need to call the image upload, accepts a blobInfo object which contains functions to give me
The file name doesn't work, neither does the blob -- both give me server errors 500 -- the error message isn't more specific.
I would prefer to use a GraphQL client to upload the image -- another SO article suggests using apollo-upload-client. However, I'm operating within the KeystoneJS environment, and Apollo-upload-client says
Apollo Client can only have 1 “terminating” Apollo Link that sends the
GraphQL requests; if one such as apollo-link-http is already setup,
remove it.
I believe Keystone has already set up Apollo-link-http (it comes up multiple times on search), so I don't think I can use Apollo-upload-client.
The UploadLink is just a drop-in replacement for HttpLink. There's no reason you shouldn't be able to use it. There's a demo KeystoneJS app here that shows the Apollo Client configuration, including using createUploadLink.
Actual usage of the mutation with the Upload scalar is shown here.
Looking at the source code, you should be able to use a custom image handler and call blob on the provided blobInfo object. Something like this:
tinymce.init({
images_upload_handler: async function (blobInfo, success, failure) {
const image = blobInfo.blob()
try {
await apolloClient.mutate(
gql` mutation($image: Upload!) { ... } `,
{
variables: { image }
}
)
success()
} catch (e) {
failure(e)
}
}
})
I used to have the same problem and solved it with Apollo upload link. Now when the app got into the production phase I realized that Apollo client took 1/3rd of the gzipped built files and I created minimal graphql client just for keystone use with automatic image upload. The package is available in npm: https://www.npmjs.com/package/#sylchi/keystone-graphql-client
Usage example that will upload github logo to user profile if there is an user with avatar field set as file:
import { mutate } from '#sylchi/keystone-graphql-client'
const getFile = () => fetch('https://github.githubassets.com/images/modules/logos_page/GitHub-Mark.png',
{
mode: "cors",
cache: "no-cache"
})
.then(response => response.blob())
.then(blob => {
return new File([blob], "file.png", { type: "image/png" })
});
getFile().then(file => {
const options = {
mutation: `
mutation($id: ID!, $data: UserUpdateInput!){
updateUser(id: $id, data: $data){
id
}
}
`,
variables: {
id: "5f5a7f712a64d9db72b30602", //replace with user id
data: {
avatar: file
}
}
}
mutate(options).then(result => console.log(result));
});
The whole package is just 50loc with 1 dependency :)
The easies way for me was to use graphql-request. The advantage is that you don't need to set manually any header prop and it uses the variables you need from the images_upload_handler as de docs describe.
I did it this way:
const { request, gql} = require('graphql-request')
const query = gql`
mutation IMAGE ($file: Upload!) {
createImage (data:
file: $file,
}) {
id
file {
publicUrl
}
}
}
`
images_upload_handler = (blobInfo, success) => {
// ^ ^ varibles you get from tinymce
const variables = {
file: blobInfo.blob()
}
request(GRAPHQL_API_URL, query, variables)
.then( data => {
console.log(data)
success(data.createImage.fileRemote.publicUrl)
})
}
For Keystone 5 editorConfig would stripe out functions, so I clone the field and set the function in the views/Field.js file.
Good luck ( ^_^)/*

How to know which data has been updated recently in google sheet using API v4

I am using node js google drive meta data readonly and spreadsheet scope for reading data with google API v4. But how to get last updated rows , columns and data that has been updated in a sheet ?
****Update**
var fetchChanges = function(pageToken,pageFn, callback) {
service.changes.list({
auth:oauth2Client,
pageToken: pageToken,
fields: 'kind,nextPageToken,newStartPageToken,changes(kind,type,time,removed,fileId,file,teamDriveId,teamDrive)'
}, function (err, res) {
if (err) {
console.log("------err in service.changes : " ,err);
return;
} else {
console.log("change resp : ", res.changes);
// Process changes
res.changes.forEach(function (change) {
console.log("Change found for file: ", change.fileId);
});
if (res.newStartPageToken) {
// Last page, save this token for the next polling interval
callback(null, res.newStartPageToken);
// console.log("--------newStartPageToken : ",res.newStartPageToken);
}
if (res.nextPageToken) {
pageFn(res.nextPageToken, pageFn, callback);
}
}
});
};
Response I am getting in console as well as in google explorer:
{ kind: 'drive#changeList',
newStartPageToken: '7911',
changes: [] }
}
Note changes object data not present and nextPageToken also absent.
I got the solution :
I have to get the future changes token at first then using that token changes done after the token creation. And remember to add the space as parameters like this:
spaces(drive,appDataFolder,photos)
There is currently no API you could use to directly retrieve the data from the last edit in a Spreadsheet.
As a workaround, you could implement an App Script that detects changes in the Spreadsheet, stores the data you need temporarily (to a different sheet), and then access it from there.
Also see these posts:
https://webapps.stackexchange.com/q/33372/154267
https://webapps.stackexchange.com/q/31894/154267
Automatic timestamp when a cell is filled out

loopback remote method return variable other than request data

I have a generic SendMail route which I want to create multiple remote methods to handle multiple request templates. Any ideas on how to return a Email_Type from the remote method back to the base route. I know I could add a default with a code in it, but would like a more elegant solution.
Mail.genericSendMail = function genericEmail(response, callback) {
console.log(response);
let templateId=0;
//PROBLEM: HOW TO KNOW WHICH REMOTE WAS USED
switch (response.emailType) {
case "Template-1":
templateId= 1234;
break;
case "Template-2":
tempalteId = 456;
break;
default:
templateId = 789l
} //switch
console.log(templateId);
};
//Want multiple routes like this to support various templates
Mail.remoteMethod("genericEmail", {
http: {
path: "/emailTemplate1",
verb: "POST"
},
accepts [
{arg: "request", type:"object",http: {source:"body"},
default: {firstName:"", lastName:"",emailAddress:""}
}],
returns: RESTResponseStatic.loopbackAdapterCommonRestResponseDefinition()
});
//Want multiple routes like this to support various templates
Mail.remoteMethod("genericEmail", {
http: {
path: "/emailTemplate2",
verb: "POST"
},
accepts [
{arg: "request", type:"object",http: {source:"body"},
default: {emailAddress:"", promoCode:""}
}],
returns: RESTResponseStatic.loopbackAdapterCommonRestResponseDefinition()
});
There are a couple of different ways to do this. Since it happens to be a POST request, I usually go with attaching data to the body using a before remote hook.
Let's say you have a model method for logging in users.
Say we have a multi realm platform, so we need to know what platform we are logging in. If you don't use realms or don't know what they are, don't worry. This just shows you how to populate the data to the model method.
User.login = function(data, cb) {
if (data.realm == 'platform1) {
return logUserIntoPlatform1(data, cb);
}
return logUserIntoDefaultPlatform(data, cb);
}
Now let's say you don't want the client/frontend to send the realm and you don't want to do the lookup for realm in the model. We can add a beforeRemote hook like so:
User.beforeRemote('login', function (context, user, next) {
context.args.data.realm = lookUpRealmSync(context); // 1
next();
});
This will be called before the login method. Note the next() call: this is how you could do error detection before actually hitting the model method. Something like next({ status: 422, message: 'Parameter missing: password }); would return an error and not execute the User.login method.
You may have to look carefully at your context object (i.e. the line marked with 1 may not work exactly as I've shown for you).
If you want to read more about this stuff, I LoopBack's docs are pretty good. It seems they've been updated since I've last used them so I can't link you to the more useful pages. I found the remote method documentation here though.
Edit: I took a closer look at your question. You should be able to retrieve the path from the context object and pass data accordingly. I'm not going to try to code that since I don't know where it would actually be within the object.

Google+ insert moment with nodejs client

Has anyone been able to get the google-api-nodejs-client to successfully insert a moment?
Whatever I try, I get a generic 400 "Invalid value" error but am unable to narrow down the invalid value because the API Explorer doesn't work either.
Would it be because of the missing data-requestvisibleactions parameter? I'm using passport.js's require('passport-google-oauth').OAuth2Strategy for handling oauth access, and that part is working fine, but I have no idea how to incorporate requestvisibleactions into the oauth request flow since this is definitely not originating from a clientside form.
Here's a snippet of what I'm trying to do (using the latest version of googleapis, v1.0.2):
var google = require('googleapis')
var auth = new google.auth.OAuth2()
auth.setCredentials({
'access_token': user.token
})
google.plus('v1').moments.insert({
collection: 'vault',
userId: 'me',
debug: true,
resource: {
type: "http://schemas.google.com/AddActivity",
target: {
type: "http://schema.org/CreativeWork",
url: "...omitted...",
image: "...omitted...",
description: "test",
name: "test"
}
},
auth: auth
}, function (err, response) {
if (err) {
console.error(err)
res.send(err.code, err)
} else {
console.log(response)
res.send(200)
}
})
ref 1 (out-of-date w.r.t. an older version of googleapis)
ref 2 (client-side, where the use of data-requestvisibleactions is more obvious)
As you speculated, you need the request_visible_actions parameter as part of the URL calling the oauth endpoint.
It looks like the current version of passport-google-oauth doesn't support this parameter. Judging by several of the open issues and pull requests, it isn't clear that the author will respond to requests to add it either. You have two possible options:
Switch to using the OAuth support that is included in google-api-nodejs-client
Patch the passport-google-oauth code. (And possibly submit a pull request in the hopes it will be useful to someone else.)
I don't use passport.js or the passport module in question, so I can't test this, but based on the github repository, I think you can insert the following in lib/passport-google-oauth/oauth2.js after line 136 and before the return statement:
if (options.requestVisibleActions) {
// Space separated list of allowed app actions
// as documented at:
// https://developers.google.com/+/web/app-activities/#writing_an_app_activity_using_the_google_apis_client_libraries
// https://developers.google.com/+/api/moment-types/
params['request_visible_actions'] = options.requestVisibleActions;
}

Resources