Http PUT deletes data that hasn't been changed - javascript

I am trying to edit a JSON data base of shifts. I am writing in Javascript using react.
This is my understanding of the PUT syntax:
const editShift = async (changed, id) => {
const res = await fetch(`http://localhost:5000/shifts/${id}`, {
method: 'PUT',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify(changed)
})
const data = await res.json()
setShifts([...shifts, data])
}
data.json:
{
"shifts": [
{
"title": "test",
"startDate": "2018-06-25T07:30:00.000Z",
"endDate": "2018-06-25T08:00:00.000Z",
"allDay": false,
"id": 1
},
{
"title": "test2",
"startDate": "2018-06-28T07:30:00.000Z",
"endDate": "2018-06-28T08:00:00.000Z",
"allDay": false,
"id": 2
}
]
}
The result is that the new shift will hold only the fields that have been changed and delete the rest. Any ideas why?

To get previous shifts you should use a callback inside setShifts like:
setShifts(prevShifts => ([...prevShifts, data]))

The PUT request should work like that.
A PUT request should PUT the data into place of the old record.
You would want to use a PATCH request for only PATCHing data upon the old record.
That set aside, it depends on your /shifts endpoint on how this is actually handled.

I had to send the whole updated shift into the changed field, with the fields that were not changed. I don't know why but it works so okay.

For a question like this, it'd be helpful if you posted before and after examples of data
database empty
request #1, payload equals X
request #2, payload equals Y
Also, knowing what your Server handler is doing would help determine what's going on. I have a feeling what's happening is something like this
// API handler
(req) => {
const dbData = getDBData();
dbData.shifts = req.shifts; // overwrite old with new
}
When what you need is something like
(req) => {
const dbData = getDBData();
req.shifts.forEach((newShiftData) => {
const shiftId = newShiftData.id;
if (dbData[shiftId]) {
dbData[shiftId] = { ...dbData[shiftId], ...newShiftData }; // merge old with new
}
});
// - check if data changed
// - update DB
}

PUT requests replace all the data at the target URI. They don't automatically merge changes.
It's not clear if you are the author of this API or just a consumer, but if you are consuming the API you should either look for a different API that lets you only send edits (HTTP method might be POST or PATCH), or you need to send the entire list of shifts.
Alternatively it might also be possible that there's a specific endpoint for each shift, so you can send exactly 1 PUT request to a shift endpoint just to edit that specific endpoint.

Related

GET request to webpage returns string, but cannot use it

I am creating a google chrome extension, and when I make a get request to this web page https://www.rightmove.co.uk/property-for-sale/find.html?locationIdentifier=REGION%5E27675&maxBedrooms=2&minBedrooms=2&sortType=6&propertyTypes=&mustHave=&dontShow=&furnishTypes=&keywords=, I get the webpage HTML as a response which is what I want (the website I am requesting info from does not have an API and I cannot web scrape for reasons too long to explain here). This response comes in the form of a string. When I attempt to split this string at a certain point, bis_skin_checked, I am returned an array of length 1, meaning that there was no match and nothing has been split. But when I look at the string returned it has it included.
I have tried things like removing spaces and carriage returns but nothing seems to be working. This is my GET request code:
function getNewPage(url) {
let returnedValue = fetch(url, {
method: 'GET',
headers: {
'Content-Type': 'text/html',
},
})
.then(response => response.text())
.then(text => {
return text
})
return returnedValue
}
I then go on to resolve the promise which is returnedValue:
let newURL = getHousePrices(currentUrl) // Get Promise of new page as a string
newURL.then(function(value) { // Resolve promise and do stuff
console.log(value.split('bis_skin_checked').length)
})
And then work with the string which looks like this: (I have attached an image as I cannot copy the text from the popup)
Image Link To API Request return
I'm assuming you want to get the home values, given certain search parameters.
Scraping raw text is usually not the way to go. I took a look at the site and saw it uses uniform network requests that you can modify to capture the data you need directly instead of scraping the raw HTML.
I built a solution that allows you to dynamically pass whatever parameters you want to the getHomes() function. Passing nothing will use the default params that you can use as a baseline while you try to adjust the request for any additional modifications/use cases.
Install the below solution and run getHomes() from the service worker.
Here's a brief video I made explaining the solution: https://vimeo.com/772275354/07fd1025ed
--- manifest.JSON ---
{
"name": "UK Housing - Stackoverflow",
"description": "Example for how to make network requests and mimic them in background.js to avoid web scraping raw text",
"version": "1.0.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"host_permissions": [
"*://*.rightmove.co.uk/*"
]
}
--- background.js ---
async function getHomes(passedParams) {
const newParams = passedParams ? passedParams : {}; // set to an empty object if no new params passed - avoid error in object.entries loop.
// starts with default params for a working request, you can then pass params to override the defaults to test new requests.
var params = {
"locationIdentifier": "REGION%5E27675",
"maxBedrooms": "2",
"minBedrooms": "1",
"numberOfPropertiesPerPage": "25",
"radius": "0.0",
"sortType": "6",
"index": "0",
"viewType": "LIST",
"channel": "BUY",
"areaSizeUnit": "sqft",
"currencyCode": "GBP",
"isFetching": "false"
}
Object.entries(params).forEach(([key, value]) => {
// we iterate through each key in our default params to check if we passed a new value for that key.
// we then set the params object to the new value if we did.
if (newParams[key]) params[key] = newParams[key];
});
const rightMoveAPISearch = `https://www.rightmove.co.uk/api/_search?
locationIdentifier=${params['locationIdentifier']}
&maxBedrooms=${params['maxBedrooms']}
&minBedrooms=${params['minBedrooms']}
&numberOfPropertiesPerPage=${params['numberOfPropertiesPerPage']}
&radius=${params['radius']}
&sortType=${params['sortType']}
&index=${params['index']}
&viewType=${params['viewType']}
&channel=${params['channel']}
&areaSizeUnit=${params['areaSizeUnit']}
&currencyCode=${params['currencyCode']}
&isFetching=${params['isFetching']}
`.replace(/\s/g, ''); // removes the whitespaces we added to make it look nicer / easier to adjust.
const data = await
fetch(rightMoveAPISearch, {
"method": "GET",
})
.then(data => data.json())
.then(res => { return res })
if (data.resultCount) {
console.log('\x1b[32m%s\x1b[0m', 'Request successful! Result count: ', parseInt(data.resultCount));
console.log('All data: ', data);
console.log('Properties: ', data.properties);
}
else console.log('\x1b[31m%s\x1b[0m', `Issue with the request:`, data)
return data
}
Hope this is helpful. Let me know if you need anything else.

How to update a certain field in object using the Nodejs Request Patch while keeping the others as before

I have a API which returns the JSON data something like this:
{
"data":{
"id": "859545",
"name":"Batman",
"custom-fields": {
"--f-09":"custom-1",
"--f-10":"custom-2",
"--f-11":"custom-3"
},
"tags": [],
"created-at": "2021-09-10T15:45:16Z",
"updated-at": "2022-04-23T11:52:49Z"
}
}
For this JSON I would like to change the field "--f-09" to "custom-1, custom-new" and "--f-10" to "custom-2, custom-new" while keeping all other fields as before.
I am aware that I can use request.PATCH in Nodejs for this but in that case, I need to provide all the data again for the request which I would like to avoid. I just want to update certain fields while keeping others as before.
In this example, I have provided a simple example which contains only certain fields but in my real code I have many fields so does this mean that I need to build the response body json using all the fields again and just change the --f-09 and --f-10?
Following is the code:
const jsonBody = {
"data": {
"id": "859545",
"name": "Batman",
"custom-fields": {
"--f-09": "custom-1, custom-new",
"--f-10": "custom-2, custom-new",
"--f-11": "custom-2"
},
"tags": [],
"created-at": "2021-09-10T15:45:16Z",
"updated-at": "2022-04-23T11:52:49Z"
}
}
request.patch('https://reqres.in/api/users',jsonBody,function (error, response, body) {
if (!error && response.statusCode == 200) {
console.log(body);
console.log(response.statusCode);
}
}
);
Does this mean that I need to build the complete JSON body again here just like I have built the jsonBody above while using the PATCH or is there any other way where I can just pass the value for --f-09 and --f-10?
First you check, what parameters do you want update is side the object. then you can filter , that values only. then you can merge separate parts in the object.

FCM message payload errors, Nested object within data object errors out

According to the react-native-fcm package you can include a custom nested object within the data object for a FCM messaging payload.
according to this post by the package author
Like this:
var payload = {
data: {
custom_notification: {
title: 'title',
body: 'body',
priority: 'high',
id: 'id',
group: 'group'
}
}
};
This is for the purpose of receiving heads up notifications in all app states, which will not happen if you do just a notification payload or data payload.
When I implement this in my cloud function I get the following error:
Error: Messaging payload contains an invalid value for the "data.custom_notification" property. Values must be strings.
So I'm at a loss as to how others can be using this successfully?
I wonder if there's some issue going on with my environment or something as the following test payload which was given to me by firebase support (and is in the docs) errors out:
var payload = {
"to":"FCM_TOKEN",
"data": {
"type":"MEASURE_CHANGE",
"body": "test body",
"title": "test title",
"color":"#00ACD4",
"priority":"high",
"id": "id",
"show_in_foreground": true
}
};
I get the following error:
Error sending message stringify: {"code":"messaging/invalid-payload","message":"Messaging payload contains an invalid \"to\" property. Valid properties are \"data\" and \"notification\"."}
Been at this for days so hopefully I can get a bit of help on this.
Thanks in advance!
So I realised just now (after days of searching) that the package react-native-fcm is using a different send method than admin.messaging().sendToDevice(token, payload, options). I had been using that for a while now and didn't realize that it was not actually what was intended to be used with this library or atleast in this scenario. Mainly because everything was working fairly well using admin.messaging() up until I wanted heads up notifications in all app states.
The other method is like this
sendData(token) {
let body = {
"to": token,
"data":{
"title": "Simple FCM Client",
"body": "This is a notification with only DATA.",
"sound": "default",
"click_action": "fcm.ACTION.HELLO",
"remote": true
},
"priority": "normal"
}
this._send(JSON.stringify(body), "data");
}
_send(body, type) {
let headers = new Headers({
"Content-Type": "application/json",
"Content-Length": parseInt(body.length),
"Authorization": "key=" + FirebaseConstants.KEY
});
fetch(API_URL, { method: "POST", headers, body })
.then(response => console.log("Send " + type + " response", response))
.catch(error => console.log("Error sending " + type, error));
}
You can use nested objects within the data object using this method. The documentation is not super clear on this unfortunately, I didn't even realise there was an example until now. Of course this could have just been me.
When using the data message payload, it is stated to use key value pairs that are String, so what you could do is have the value of your custom_notification as JSON String by enclosing them in " ".
For the sample payload provided, are you actually using the FCM_TOKEN in the to parameter? You're supposed to replace it with an actual token.

How do I delete a list of files meeting criteria with Google Drive API and Javascript?

I want to get a list of things with get list (which I have already coded in), but next, I want a delete button that deletes all the things on that list. How can I do that with Google Drive SDK and Javascript?
Example, if my criteria is:
q: "starred = "+test+" and viewedByMeTime < '"+n+""
How do I delete every single file that meets that criteria?
References: https://developers.google.com/drive/v2/reference/files/delete
https://developers.google.com/drive/v2/web/search-parameters
I tried fitting search file and delete files together, but I am really bad at it and I am not sure which variable represents all the files found matching the criteria and how to get that variable to be fileID:
function DeleteFiles() {
var x = document.getElementById("ResultShown").value;
var date = new Date();
date.setDate(date.getDate() - 180);
var n = date.toISOString().split('.')[0] ;
var test = false;
gapi.client.drive.files.list({
pageSize: x,
q: "starred = "+test+" and viewedByMeTime < '"+n+"'",
fields: "nextPageToken, files(id)",
}
).then(function deleteFile(fileId)) {
var request = gapi.client.drive.files.delete({
appendPre('Files:');
var files = response.result.files;
if (files && files.length > 0) {
for (var i = 0; i < files.length; i++) {
var file = files[i];
'fileId': fileId
});
request.execute(function(resp) { })
}
}
});
}
So using the Javascript Drive API, you will first issue a GET to the files endpoint, using the list action which supports your query parameter q. It sounds like you already have that figured out.
You will get a response object -- to get more specific, you would need to post some code. Are you using VanillaJS XMLHttpRequest, jQuery's $.ajax(), perhaps the delicious and native async fetch() API? Either way, you will get a response which can be parsed to JSON.
As you have discovered, the Drive API does not support permanently deleting multiple Drive resources in one request. At this point, you may be rethinking the architecture of your application: removing a very long list could require many HTTP requests. This could generate significant overhead, so you might want to limit how long lists can be or do something else clever. If you decide to take this route, I'd suggest using fetch to use the API asynchronously.
So after you call JSON.parse(responseBody) (or responseBody.json() if you're using fetch()), you will get something from Google API that looks like this:
{
"kind": "drive#fileList",
"etag": "...",
"selfLink": "https://www.googleapis.com/drive/v2/files",
"nextPageToken": "...",
"nextLink": "...",
"incompleteSearch": false,
"items": [
{
"kind": "drive#file",
"id": "SOME FILE ID",
"etag": "...",
"selfLink": "...",
"alternateLink": "...",
"embedLink": "...",
"openWithLinks": {
...
},
{
"kind": "drive#file",
"id": "SOME FILE ID",
"etag": "...",
"selfLink": "...",
"alternateLink": "...",
"embedLink": "...",
"openWithLinks": {
...
}
]
So what you need is:
A function which:
Is called when your GET to files.list is complete.
Loops through responseJSON.items and calls the function below with an itemId.
A function which issues an HTTP DELETE given an ID.
So without any specific details of your implementation, a general solution for your purpose might look like:
var apiEndpoint = new URL("https://www.googleapis.com/drive/v2/files"),
params = {q: "some stuff", otherParams: "lol"}
// assemble GET querystring in a nice way
Object.keys(params).forEach(function(key) {
apiEndpoint.searchParams.append(key, params[key]);
});
// go and fetch! (explicit HTTP header options optional but good practice)
fetch(apiEndpoint , {
method: 'GET',
mode: 'cors',
redirect: 'follow',
headers: new Headers({
'Content-Type': 'application/json'
})
});
.then(function (responseBody) {
return responseBody.json();
})
.then(function (responseObj) {
for(i=0; i<responseObj.items.length; i++) {
asyncDeleteDriveItem(responseObj.items[i].id);
}
});
});
function asyncDeleteDriveItem() {/* ... */}

Ember ActiveModelAdapter URL without underscore?

i have this json response to GET-request => /checklists
{"check_lists": [
{
"id": 1,
"name": "Example-list-1",
"description": ""
},
{
"id": 2,
"name": "Example-list-1",
"description": ""
}
}
To handle the underscored naming-convention of this server, i use the ActiveModelAdapter and ActiveModelSerializer.
The problem i got stucked on is the request-url.
My model is named App.CheckList = DS.Model.extend({... and thats the point where it starts to get complicated.
if i call
return this.store.find('checkList');
in my route, Ember start a GET request to /checkLists route instead of /check_lists =>
GET http://localhost:3000/checkLists 404 (Not Found)
Error while processing route: checklists
surprisingly for this error the
buildURL: function(type, id) {
return this._super(type, id);
},
is not used so i have no chance to modify the url.
Does anyone know how to change the request to /check_lists ??
The mapping of camelized case and underscored case is done in ActiveModelAdapter.pathForType function. You can override it and make your changes there. For instance, to change from camelized case to underscored case:
App.ApplicationAdapter = DS.ActiveModelAdapter.extend({
pathForType: function(type) {
var decamelized = Ember.String.decamelize(type);
var underscored = Ember.String.underscore(decamelized);
//Alternatively, you can change urls to dasherized case using this line
//var dasherized = Ember.String.dasherize(decamelized);
return Ember.String.pluralize(underscored);
}
});
The strange part is that the latest Ember Data already has this code in ActiveModelAdapter. You may want to check the version you are running and upgrade to that instead of the change I suggest above.

Categories

Resources