POST Request to PUT Request - javascript

"
An open source course that I am taking has the following prompt for fullstack development:
3.17: Phonebook database, step5
If the user tries to create a new phonebook entry for a person whose name is already in the phonebook, the frontend will try to update the phone number of the existing entry by making an HTTP PUT request to the entry's unique URL.
Modify the backend to support this request.
Verify that the frontend works after making your changes."*
It appears that this would call for an express app.put request called inside on an app.post request. Is there something that I'm missing here? How you handle this kind of logic with mongoDB/mongoose/expresss? My current post code is pasted below.
Thanks
app.post('/api/persons',(req,res) => {
const body = req.body
const containsNameNumber = body.name && body.number
if(!containsNameNumber){
return res.status(400).json({
error:"must specify a name and a number"
})
}
const phone = new Person({
name: body.name,
number:body.number
})
phone.save().then(savedPerson => {
res.json(savedPerson)
})
})

I think you are missunderstanding one thing, check PUT definition from RFC7231
The PUT method requests that the state of the target resource be
created or replaced with the state defined by the representation
enclosed in the request message payload
Is importante the part "be created". So with a PUT if the resource exists it will be updated but if not exists will be created.
If the target resource does not have a current representation and the
PUT successfully creates one, then the origin server MUST inform the
user agent by sending a 201 (Created) response.
So you only have to do a PUT call and check if exists to update (return 200 Ok) or not exists to create (return 201 Created).

Related

UserState showing null in onMembersAdded function

I have logic in my onMembersAdded function to load the user state and see if userData.accountNumber attribute exists. If it does not, a run an auth dialog to get the user's account number. If the attribute does exist, the welcome message should be displayed without a prompt.
When I test on local, this works fine. But when I test on Azure, I always end up in the !userData.accountNumber block. Through checking the console log, I can see that in the onMembersAdded function is showing {} for the userData object. But in auth dialog, even if I skip the prompt (which we allow the user to do), the accountNumber attribute is there in userState (if it had been entered previously).
The only thing I can figure is that somehow using BlobStorage for state, as I do on Azure, is somehow exhibiting different behavior than MemoryStorage which I am using for local testing. I thought it might be a timing issue, but I am awaiting the get user state call, and besides if I do enter an account number in the auth dialog, the console log immediately following the prompt shows the updated account number, no problem.
EDIT: From the comments below, it's apparent that the issue is the different way channels handle onMembersAdded. It seems in emulator both bot and user are added at the same time, but on webchat/directline, user isn't added until the first message is sent. So that is the issue I need a solution to.
Here is the code in the constructor defining the state variables and onMembersAdded function:
// Snippet from the constructor. UserState is passed in from index.js
// Create the property accessors
this.userDialogStateAccessor = userState.createProperty(USER_DIALOG_STATE_PROPERTY);
this.dialogState = conversationState.createProperty(DIALOG_STATE_PROPERTY);
// Create local objects
this.conversationState = conversationState;
this.userState = userState;
this.onMembersAdded(async (context, next) => {
const membersAdded = context.activity.membersAdded;
for (let member of membersAdded) {
if (member.id === context.activity.recipient.id) {
this.appInsightsClient.trackEvent({name:'userAdded'});
// Get user state. If we don't have the account number, run an authentication dialog
// For initial release this is a simple prompt
const userData = await this.userDialogStateAccessor.get(context, {});
console.log('Members added flow');
console.log(userData);
if (!userData.accountNumber) {
console.log('In !userData.accountNumber block');
const dc = await this.dialogs.createContext(context);
await dc.beginDialog(AUTH_DIALOG);
await this.conversationState.saveChanges(context);
await this.userState.saveChanges(context);
} else {
console.log('In userData.accountNumber block');
var welcomeCard = CardHelper.GetHeroCard('',welcomeMessage,menuOptions);
await context.sendActivity(welcomeCard);
this.appInsightsClient.trackEvent({name:'conversationStart', properties:{accountNumber:userData.accountNumber}});
}
}
}
// By calling next() you ensure that the next BotHandler is run.
await next();
});
If you want your bot to receive a conversation update from Web Chat with the correct user ID before the user sends a message manually, you have two options:
Instead of connecting to Direct Line with a secret, connect with a token (recommended). Note that this will only work if you provide a user property in the body of your Generate Token request.
Have Web Chat send an initial activity to the bot automatically so the user doesn't have to. This would be in response to DIRECT_LINE/CONNECT_FULFILLED, and it could be an invisible event activity so to the user it still looks like the first activity in the conversation came from the bot.
If you go with option 1, your bot will receive one conversation update with both the bot and the user in membersAdded, and the from ID of the activity will be the user ID. This is ideal because it means you will be able to acess user state.
If you go with option 2, your bot will receive two conversation update activities. The first is the one you're receiving now, and the second is the one with the user ID that you need. The funny thing about that first conversation update is that the from ID is the conversation ID rather than the bot ID. I presume this was an attempt on Web Chat's part to get the bot to mistake it for the user being added, since Bot Framework bots typically recognize that conversation update by checking if the from ID is different from the member being added. Unfortunately this can result in two welcome messages being sent because it's harder to tell which conversation update to respond to.
Conversation updates have been historically unreliable in Web Chat, as evidenced by a series of GitHub issues. Since you may end up having to write channel-aware bot code anyway, you might consider having the bot respond to a backchannel event instead of a conversation update when it detects that the channel is Web Chat. This is similar to option 2 but you'd have your bot actually respond to that event rather than the conversation update that got sent because of the event.
Per Kyle's answer, I was able to resolve the issue. However, the documentation on initiating a chat session via tokens wasn't entirely clear, so I wanted to provide some guidance for others trying to solve this same issue.
First, you need to create an endpoint in your bot to generate the token. The reason I initiated the session from SECRET initially was because I didn't see a point to creating a token when the SECRET was exposed anyway to generate it. What wasn't made clear in the documentation was that you should create a separate endpoint so that the SECRET isn't in the browser code. You can/should further obfuscate the SECRET using environmental variables or key vault. Here is the code for the endpoint I set up (I'm passing in userId from browser, which you'll see in a minute).
server.post('/directline/token', async (req, res) => {
try {
var body = {User:{Id:req.body.userId}};
const response = await request({
url: 'https://directline.botframework.com/v3/directline/tokens/generate',
method: 'POST',
headers: { Authorization: `Bearer ${process.env.DIRECTLINE_SECRET}`},
json: body,
rejectUnauthorized: false
});
const token = response.token;
res.setHeader('Content-Type', 'text/plain');
res.writeHead(200);
res.write(token);
res.end();
} catch(err) {
console.log(err);
res.setHeader('Content-Type', 'text/plain');
res.writeHead(500);
res.write('Call to retrieve token from Direct Line failed');
res.end();
}
})
You could return JSON here, but I chose to return token only as text. Now to call the function, you'll need to hit this endpoint from the script wherever you are deploying the bot (this is assuming you are using botframework-webchat CDN). Here is the code I used for that.
const response = await fetch('https://YOURAPPSERVICE.azurewebsites.net/directline/token', {
method: 'POST',
headers: {'Content-Type':'application/json'},
body: JSON.stringify({userId:userID})
});
const token = await response.text();
Body of request must be stringified JSON. Fetch returns the response as a stream, so you need to convert it using .text() or .json() depending on how you are sending the response from your bot endpoint (I used .text()). You need to await both the fetch AND the response.text(). My whole script to deploy the webchat is within an async function. Just a note, if you need this to work in IE11 as I do, async/await won't work. I dealt with this by running the entire code through Babel once I was done and it seems to work fine.

why is that making GET request with search param won't return 404 even when the no resources have been found

I encountered this weird problem when experimenting with JavaScript's URL object
here is the demo : https://codesandbox.io/s/fervent-wilbur-15kyt?file=/src/index.js
so the endpoint is https://jsonplaceholsdsdsdder.typicode.com/todos/,
The key id can be integer as its value. So https://jsonplaceholder.typicode.com/todos/?id=4 is valid. and https://jsonplaceholder.typicode.com/todos/?id=dsdsd is not valid.
I found that using Fetch to make the request https://jsonplaceholder.typicode.com/todos/?id=4 will still return a response with a status code 200.
const inputEl = document.querySelector("#input");
const endpoint = new URL("https://jsonplaceholder.typicode.com/todos/");
inputEl.addEventListener("input", async e => {
const { value: text } = e.target;
endpoint.searchParams.set("id", text);
const repsonse = await fetch(endpoint).catch(console.error);
const data = await repsonse.json();
console.log(repsonse.status); // 200
console.log(data); // []
});
However if we construct the URL directly like this
https://jsonplaceholsdsdsdder.typicode.com/todos/dd. this will actually return a response with 404 status code.
search params are not a part of the resource location. They are optional and have to be manually accessed by the server processing the request.
https://jsonplaceholder.typicode.com/todos/ is a valid resource location that has been set up by jsonplaceholder.com.
https://jsonplaceholder.typicode.com/todos/?id=4 is a valid resource location because anything after the question mark (?) is a parameter and parameters are not considered to be apart of the resource location so it is still valid.
https://jsonplaceholsdsdsdder.typicode.com/todos/dd is not a valid resource location because jsonplaceholder.com has not exposed a resource with that path.
This is because of the way the API works server side.
If you try and use that route, you'll get the 404 error because that specific route wasn't found.
If you use the searchParams.set method, it's just a parameter that the backend will use to filter the full list of todos, in which case the call was made successfully, hence the 200 response code. but the response results were empty, hence the empty array [].

Zapier custom response object

Working on creating a custom zapier integration using zapier CLI. My API endpoint is not technically a create, but it uses the POST method so I made it under the create definition in zapier. I set my output fields to empty, but it breaks on my empty response object.
outputFields: []
The error message:
We had trouble sending your test through.
Unexpected end of JSON input
Hide details
Troubleshooting Errors | Contact Support
What happened (You are seeing this because you are an admin):
Starting POST request to https://api.fake.com/v2/demo-finance/live/csh-search
Received 202 code from https://api.fake.com/v2/demo-finance/live/csh-search after 596ms
Received content ""
Unexpected end of JSON input
Everything is working as expected the request went through it is just not happy with the empty string response not being valid JSON. Is there some way to tell zapier this is an acceptable response object?
David here, from the Zapier Platform team.
It's fine if your API works that way, but you still need to return something json serializable from your function. Try something like this:
const performCreate = async (z, bundle) => {
const response = await z.request('https://api.fake.com/v2/demo-finance/live/csh-search')
if (response.statusCode === 202) {
return {}
}
// handle errors, other cases, whatever
// just make sure to return an object
}
As a side note, just because the request uses a POST request doesn't mean it needs to be a Create; it should be whatever type makes the most sense for the operation. If it's a search (like the fake url suggests) a search is probably the way to go.

get instagram public media using new api

I am trying to get the 10 latest instagram photos of a public profile in my nodejs app:
const request = require('request');
const accessToken = '1234567890123456789012345678901234567890';
const url = `https://api.instagram.com/v1/users/1470414259/media/recent/?access_token=${accessToken}&count=10`;
request.get(url, { json: true }, (err, res, body) => {
console.log('print the body: ', body);
});
my attempt is not successful, and i get no results.
Could somebody help me get this going?
i am trying to follow the example here : https://www.instagram.com/developer/endpoints/users/#get_users_media_recent
update:
i updated the username to user ID, but i am still getting no response:
{ meta:
{ code: 400,
error_type: 'APINotFoundError',
error_message: 'this user does not exist' } }
update 2:
actually i realize that my access token has some restrictions:
{"meta": {"code": 400, "error_type": "OAuthPermissionsException", "error_message": "This request requires scope=public_content, but this access token is not authorized with this scope. The user must re-authorize your application with scope=public_content to be granted this permissions."}}
why is this happening? i am just trying to get some public profile feeds.
update 3:
is there any other way to bypass these access token permissions and just get the 10 media of a public user?
take a look at the json that this link is returning:
https://www.instagram.com/aa/?__a=1
body.user.media[0] will give you the last photo object
body.user.media[1] will give you the photo before the last one object
.. and so on.
what you have to do here is change aa in the url I provided to your desired username.
ps. you might use some json viewer tools to organize the json if you browser doesn't support that
You have two problems there:
First of all the endpoint receives the user ID as param (not the username):
const url = `https://api.instagram.com/v1/users/${userId}/media/recent/?access_token=${accessToken}&count=10`;
Besides that, the endpoint responds with an object that has only one element: data, that is an array. So I don't know what data you are trying to get but you won't get it in body.user, you should go through the array and ask for the user in the corresponding position, for example try this: console.log(body.data[0].user).
And you of course must define a valid access token!

Why is my Service Worker's push event data/payload null?

I have made multiple attempts to get desktop notifications working in Chrome, but I have not found a single source of documentation that covers a step by step procedure to get desktop notifications to work correctly. Each resource I have come across is either outdated or inconsistent with others.
The problem I am facing is: once the Service Worker receives the push event,
self.addEventListener('push', function (event) {
console.log(event);
event.waitUntil(
self.registration.showNotification(
event.data.title,
{
body: event.data.body,
icon: event.data.icon,
tag: event.data.tag
}));
});
event.data is null. I expect it to have data that I am sending as JSON in a POST request like this:
POST https://fcm.googleapis.com/fcm/send HTTP/1.1
Content-Type: application/json
Authorization: key=<FCM Server Key here>
{
"data": {
"title": "Foo",
"body": "Bar"
},
"to": "<recipient ID here>"
}
The weird thing is the registration script gets a "subscription endpoint" that looks like https://android.googleapis.com/gcm/<recipient ID here>, but I cannot get the POST to go through unless I follow other examples on the web that say to put the recipient ID as the to field in the JSON I am sending.
Of all the examples I have come across, there are multiple URLs that POST calls are being made to:
https://fcm.googleapis.com/fcm/send
https://android.googleapis.com/gcm/send
https://gcm-http.googleapis.com/gcm/send
I have tried all three, with each attempt having the recipient at the end of the API address (like https://fcm.googleapis.com/fcm/send/<recipient ID here> and alternatively in the JSON body. My goal is to get Foo and Bar from the data I am sending into the self.registration.showNotification( method of the service worker.
Why is event.data null? Can anyone point me to a complete guide from start to finish that favors FCM over GCM? Any help would be appreciated.
You may want to check the following statement from the documentation,
A downside to the current implementation of the Push API in Chrome is that you can't send any data with a push message. Nope, nothing. The reason for this is that in a future implementation, payload data will have to be encrypted on your server before it's sent to a push messaging endpoint. This way the endpoint, whatever push provider it is, will not be able to easily view the content of the push message. This also protects against other vulnerabilities like poor validation of HTTPS certificates and man-in-the-middle attacks between your server and the push provider. However, this encryption isn't supported yet, so in the meantime you'll need to perform a fetch to get information needed to populate a notification.
Reading further, you may want to try using fetch() to get data from an API, convert the response to an object and use it to populate notification. This same method was also used in this related SO post.
In addition to that, you may want to also check the response of #Indici Indici in the thread wherein he stated that push event does not contain data values; instead it contains different events which contains information(s). Here is the sample code that was provided as a possible workaround to receive notification in Firebase service-worker in "push" event:
self.addEventListener('push', function(event) {
if (event.data) {
const dataText = event.data.text();
notificationTitle = 'Custom Notification';
notificationOptions.body = 'Message: ' + `${dataText}`;
var title = event.data.notification.title;
var message = event.data.notification.message;
var icon = event.data.notification.icon;
var notificationTag = event.data.notification.tag;
}
}
For receive data need:
self.addEventListener('push', function(event) {
var jsonData = JSON.parse(event.data.text());
// jsonData -> here is you data
const options = {
body: 'set you body',
icon: 'img/apple-icon-120x120.png',
badge: 'img/apple-icon-120x120.png'
};
event.waitUntil(self.registration.showNotification(jsonData.data.title, options));
});

Categories

Resources