NextJS API routes how to handle multiple requests with MongoDB - reactjs

I have an API route. Inside of this route I handle requests coming from my components with MongodDB. My problem is that I am sending a PUT request from one of my components to that route and it is working. But now I want to send another PUT request from another component. How will I achieve this?
if (req.method === "PUT") {
try {
const { _id, id, change } = req.body;
let set = `settings.$[el].${id}`;
const data = await db
.collection("Todos")
.updateOne(
{ _id: _id },
{ $set: { [set]: change } },
{ arrayFilters: [{ "el._id": id }] }
);
res.status(201).json(data);
} catch (err) {
res.status(500).json({ message: "Unable to instert the data." });
}
}
This is for my one request and now I want to send another one but also with another data. If I send it there will conflict so it fails. Some basic solutions I found but they are not sustainable.

One way is to create a new route /api/newRoute.
Another way is when you send the request add to its body a variable that differentiates between the two. For example:
body: {
// your original data
type: "newType" // this variable can be named anything you like
}
In the api route you can use the following code example:
if (req.method === "PUT" && req.body.type == "newType") {
try {
// your new code
} catch (err) {
// your new code
}
}

Related

how intercept and stub the response of a rpc call in react with cypress

I want to intercept a rpc call that I made to the api in my react app. I'm using a custom hook that receives the buffer and the rpc method that I want to call and returns the data(something like react-query useQuery hook).
The thing is because of being a rpc call, the request urls of my requests are all the same and the response is binary, I can't distinguish the requests and intercept the one to stub.
One example of making a rpc call:
const {response, loading, error} = useRpc({
Buffer: GetUser,
Request: GetUserRequest
});
Edit 1:
I'm using
cy.fixture('fixutre-file').then((data) => {
const response = new TextDecoder().decode(res.body);
cy.intercept('https://example.com/', { method: 'POST' },
(req) => {
req.continue((res) => {
if ("some condition for distinguishing the request I want to intercept, here") {
res.send({ fixture: 'fixutre-file' });
}
});
});
}):
to get the response and decide whether or not intercept this req and instead send back my fixture data. But the response constant is still some unreadable string. What's wrong with my approach?
Edit 2:
Another approach that I used, was to use the cypress-protobuf package and encode my fixture.json file with the related protobuffer file:
cy.fixture('fixutre-file').then((data) => {
cy.task('protobufEncode', {
fixtureBody: data,
message: 'the_message',
protoFilePath: './protobuf/protofile.proto'
}).then((encodedData) => {
cy.intercept('https://example.com/', { method: 'POST' },
(req) => {
/////// approach 1(didn't work): ///////
// can't use this approach(because there is no identifier on
// req object to distinguish the requests I want to
// intercept)
// if ("some condition based on `req` here") {
// req.reply(encodedData);
// } else {
// req.continue();
// }
/////// approach 2: ///////
// using encodedData to compare it with res.body
req.continue(res => {
// can't compare res.body with encodedData, because
// encodedData is an empty string!
});
}).as('needToWait');
cy.wait('#needToWait').get('some selector').should('exist')
});
}):
Now the problem is:
encodedData is just an empty string, meaning it didn't work, so I can't compare the response with my fixture data to intercept the related request
You can simply check for some value from the request that distinguishes it from the other requests. Request bodies and headers are often good places to start. Additionally, you can use req.alias to conditionally assign an alias if you need to wait for that specific call.
cy.intercept('/foo', (req) => {
if (req.body.bar === true) { // or whatever logic indicates the call you want to intercept
req.alias = 'baz'; // conditionally assign alias
req.reply({foo: 'bar'}); // mock response
} else {
req.continue(); // do not mock response
}
});
cy.get('something')
.click()
.wait('#baz'); // waits for your specific 'baz' call to happen.

How to activate a react route and pass data from the service worker?

I have a SPA PWA React app.
It is installed and running in standalone mode on the mobile device (Android+Chrome).
Let's say the app lists people and then when you click on a person it diplays details using /person route.
Now, I'm sending push notifications from the server and receiving them in the service worker attached to the app. The notification is about a person and I want to open that person's details when the user clicks on the notification.
The question is:
how do I activate the /person route on my app from the service worker
and pass data (e.g. person id, or person object)
without reloading the app
From what I understand, from the service worker notificationclick event handler I can:
focus on the app (but how do I pass data and activate a route)
open an url (but /person is not a physical route, and either way - I want avoid refreshing the page)
You can listen for click event for the Notification which you show to the user. And in the handler, you can open the URL for the corresponding person which comes from your server with push event.
notification.onclick = function(event) {
event.preventDefault();
// suppose you have an url property in the data
if (event.notification.data.url) {
self.clients.openWindow(event.notification.data.url);
}
}
Check these links:
https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/notificationclick_event
https://developer.mozilla.org/en-US/docs/Web/API/Clients/openWindow
To answer my own question: I've used IndexedDB (can't use localStorage as it is synchronous) to communicate between SW and PWA, though I'm not too happy about it.
This is roughly how my service worker code looks (I'm using idb library):
self.addEventListener('notificationclick', function(event) {
const notif = event.notification;
notif.close();
if (notif.data) {
let db;
let p = idb.openDB('my-store', 1, {
upgrade(db) {
db.createObjectStore(OBJSTORENAME, {
keyPath: 'id'
});
}
}).then(function(idb) {
db = idb;
return db.clear(OBJSTORENAME);
}).then(function(rv) {
return db.put(OBJSTORENAME, notif.data);
}).then(function(res) {
clients.openWindow('/');
}).catch(function(err) {
console.log("Error spawning notif", err);
});
event.waitUntil(p);
}
});
and then, in the root of my react app ie in my AppNavBar component I always check if there is something to show:
componentWillMount() {
let self = this;
let db;
idb.openDB('my-store', 1)
.then(function (idb) {
db = idb;
return db.getAll(OBJSTORENAME);
}).then(function (items) {
if (items && items.length) {
axios.get(`/some-additional-info-optional/${items[0].id}`).then(res => {
if (res.data && res.data.success) {
self.props.history.push({
pathname: '/details',
state: {
selectedObject: res.data.data[0]
}
});
}
});
db.clear(OBJSTORENAME)
.then()
.catch(err => {
console.log("error clearing ", OBJSTORENAME);
});
}
}).catch(function (err) {
console.log("Error", err);
});
}
Have been toying with clients.openWindow('/?id=123'); and clients.openWindow('/#123'); but that was behaving strangely, sometimes the app would stall, so I reverted to the IndexedDB approach.
(clients.postMessage could also be the way to go though I'm not sure how to plug that into the react framework)
HTH someone else, and I'm still looking for a better solution.
I had a similar need in my project. Using your's postMessage tip, I was able to get an event on my component every time a user clicks on service worker notification, and then route the user to the desired path.
service-worker.js
self.addEventListener("notificationclick", async event => {
const notification = event.notification;
notification.close();
event.waitUntil(
self.clients.matchAll({ type: "window" }).then(clientsArr => {
if (clientsArr[0]) {
clientsArr[0].focus();
clientsArr[0].postMessage({
type: "NOTIFICATION_CLICK",
ticketId: notification.tag,
});
}
})
);
});
On your react component, add a new listener:
useEffect(() => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.addEventListener("message", message => {
if (message.data.type === "NOTIFICATION_CLICK") {
history.push(`/tickets/${message.data.ticketId}`);
}
});
}
}, [history]);

How to push an object into an array in vue

I am trying to push a newly created object to an array. The array is defined as
clientengagements: []
The Object is
engagement: []
So I am using a v-for to iterate through each engagement belonging to a client in my clientengagements array. Everything works fine until I submit a new engagement. It changes my clientengagements: [] array to only show the new object. Now if i refresh the page, the clientengagements: [] will go back to the array with the newly added object plus the other objects that already existed which is what I want it to do..
This is the AddEngagement component script that I use to dispatch to the store
methods: {
...mapActions(['addEngagement']),
addNewEngagement() {
if(!this.engagement.return_type || !this.engagement.year ) return;
this.addEngagement({
id: this.idForEngagement,
client_id: this.client.id,
return_type: this.engagement.return_type,
year: this.engagement.year,
assigned_to: this.engagement.assigned_to,
status: this.engagement.status,
})
.then(() => {
this.engagement = ""
this.idForEngagement++
this.$router.go(-1);
})
},
},
The action in the store is defined like below
addEngagement(context, engagement) {
axios.post(('/engagements'), {
client_id: engagement.client_id,
return_type: engagement.return_type,
year: engagement.year,
assigned_to: engagement.assigned_to,
status: engagement.status,
done: false
})
.then(response => {
context.commit('getClientEngagements', response.data)
})
.catch(error => {
console.log(error)
})
},
from there it should commit to the getClientEngagements() mutation which is where I believe I am running into my issue but I have not figured out how to resolve. Here is the code
getClientEngagements(state, clientengagements) {
state.clientengagements = clientengagements;
},
I have been recommended to use Vue.set() but I do not know how to apply it.. any help would be greatly appreciated!!!
So I have set the mutation getClientEngagements to use this now
getClientEngagements(state, engagement) {
state.clientengagements.push(engagement)
},
But by changing the mutation to this it has placed the already existing objects into a deeper nested array. see below
So the response.data for the new engagement only sends back from the backend that newly added engagement on the addEngagement action. is this a problem?
Thanks for all the help, but I actually ended up needing to add a extra mutation and use that as the commit, because I am defining to seperate lists of engagements. One for the client and one for all clients. here is the new mutation
addClientEngagement(state, engagement) {
state.clientengagements.push(engagement)
},
which I then use in my action here
addEngagement(context, engagement) {
axios.post(('/engagements'), {
client_id: engagement.client_id,
return_type: engagement.return_type,
year: engagement.year,
assigned_to: engagement.assigned_to,
status: engagement.status,
done: false
})
.then(response => {
context.commit('addClientEngagement', response.data)
})
.catch(error => {
console.log(error)
})
},
and then this mutation happens
getClientEngagements(state, clientengagements) {
state.clientengagements = clientengagements
},
Previously it was using this mutation to add the engagement to the array which was why it was replacing the array with the new object. see below for old mutation
addEngagement(state, engagement) {
state.engagements.push ({
id: engagement.id,
client_id: engagement.client_id,
return_type: engagement.return_type,
year: engagement.year,
assigned_to: engagement.assigned_to,
status: engagement.status,
done: false
})
},
Hopefully I am making sense in my explanation
.then(response => {
...// data transaction
context.commit('getClientEngagements', response.data)
})
Maybe you can do with the response data at first and then store it after that.
If the response data is an object, and you only want to store the value,
you can do something similar to this:
for( p in object ){
context.commit('getClientEngagements', object[p])
}
If the response data is an array, then you can try to use for to get the value you want and then push in the state.
You should do the transaction of data according to your needs at first for your case.

Passing in an array of values to an express POST route

I have a mongoose model called Todo that looks like this:
content: [{
contentType: {
type: String,
default: null
},
contentValue: {
type: String,
default: null
}
}]
My express POST route looks like this:
app.post('/todos', authenticate, (req, res) => {
var todo = new Todo({
content: req.body.content
});
res.send(todo)
//I am sending instead of sending the result for testing
});
When I send int test data using Postman the content array is coming back empty "content": []
I have tried several formats of Body in postman including:
{
"content[contentType]": "reminder",
"content[contentValue]": "Will it blend"
}
and
{
"content": {
"contentType": "reminder",
"contentValue": "Will it blend"
}
}
However both come back with empty array.
{
"content": []
}
Do I need to change something on my POST route and\or send in teh data in an alternate format?
content here is a todo sub document, so you should treat it like the following:
app.post('/todos', authenticate, (req, res) => {
var content = req.body.content;
var todo = new Todo();
todo.content.push(content);
todo.save(function(err) {
if (err) throw err;
res.json(todo.toJSON())
//I am sending instead of sending the result for testing
});
});
See: Mongoose SubDocuments

Authentication in Angular 2, handling the observables

I just started with a Angular 2 project and am trying to get authentication up and running. Inspired by this tutorial I decided to do the following:
Create a custom RouterOutlet class (extending it) to handle the authentication logic whenever a url is called.
I succeeded in this custom class, but am still not sure how to check if a user is authenticated. My situation is as follows, I need to query a get call to a external API, for my development proces it is as follows:
getAdmin() {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.get('http://localhost:3000/admin/is_admin.json', options)
.map(res => res)
.catch(this.handleError)
}
This API call returns true or false. I was wondering what would be the best option to use this information? Should I for example call the following function each time a URL should be checked?:
isAdmin() {
this.getAdmin().subscribe(
data => this.authenticationResult = data,
error => console.log("Error: ", error),
() => return JSON.parse(this.authenticationResult._data);
}
I can't get this up and running because my observable is undefined when using the function I gave as example.
The "problem" is that your method is asynchronous so you need to be careful the way and when you use it.
If you want to use within the activate method of your custom RouterOutlet, you need to leverage observables and reactive programming.
I don't know exactly the way you want to check admin roles:
activate(instruction: ComponentInstruction) {
return this.userService.getAdmin().flatMap((isAdmin) => {
if (this.userService.isLoggIn()) {
if (this._canActivate(instruction.urlPath, isAdmin) {
return Observable.fromPromise(super.activate(instruction));
} else {
this.router.navigate(['Forbidden']);
return Observable.throw('Forbidden');
}
} else {
this.router.navigate(['Login']);
return Observable.throw('Not authenticated');
}
}).toPromise();
}
_canActivate(url, admin) {
return this.publicRoutes.indexOf(url) !== -1
|| this.userService.isLoggedIn();
}
In order to optimize the request, you could lazily (and only once) call the request to check if the user is admin or not:
isAdmin:boolean;
getAdmin() {
if (this.isAdmin) {
return Observable.of(this.isAdmin);
} else {
let headers = new Headers({ 'Content-Type': 'application/json' });
let options = new RequestOptions({ headers: headers });
return this.http.get('http://localhost:3000/admin/is_admin.json', options)
.map(res => res)
.catch(this.handleError);
}
}
Another approach will be also to load this hint when authenticating the user. This way, the implementation of the activate method would be simplier:
activate(instruction: ComponentInstruction) {
if (this.userService.isLoggIn()) {
if (this.userService.isAdmin()) {
return super.activate(instruction);
} else if (this._canActivate(instruction.urlPath, isAdmin) {
return super.activate(instruction);
} else {
this.router.navigate(['Forbidden']);
}
} else {
this.router.navigate(['Login']);
}
}
_canActivate(url, admin) {
return this.publicRoutes.indexOf(url) !== -1
|| this.userService.isLoggedIn();
}
I would consider to call getAdmin() somehow as first Step of your app, store the result in a SessionService object which you move around using Dependency Injection. This way any time you need to check the result of getAdmin you can ask the SessionService instance.
I hope this helps

Resources