Best practice to fetch sync - dvajs

Im trying to get the model User which have a relation with Projects.
Here is my models/user.js
*fetchUser({ payload, callback }, { call, put }) {
const user = yield call(queryUser, payload);
yield put({
type: 'setCurrentUser',
payload: user,
});
if (callback) callback();
},
What is the best practice? Where should I call the function queryProjects?
Maybe below this line?
const user = yield call(queryUser, payload);
const projects = yield call(queryProjects, user); // new api call

First Approach
Make two models, users & projects
Then you can do this
*fetchUser({ payload, callback }, { call, put }) {
const user = yield call(queryUser, payload);
yield put({
type: 'setCurrentUser',
payload: user,
});
yield put({type: 'projects/fetchByUserId', payload: user.id});
},
Inside your `projects` model.
*fetchByUserId({payload){
// do ajax call
}
Once you have data for projects you can filter the project array with user id (considering your project has something like project.user_id) and show them into the app.
Second Approach
You can have your backend server send the project list for user in the user object.
{
id: 1,
username: "foo",
projects: [
{id: 1},
{id: 2}
{id: 3}
]
}
I hope it makes sense.

Related

VueJS Vuex Axios cURL API for Wordpress

I'm using Vuex to make all my API calls and since the beginning the doc was giving example of HTTP requests for displaying posts. But when it comes to CRUD the examples mention cURL requests instead of HTTP ones. It's the first time I'm working with API and I don't know if the syntax/the way I called the API since now is reusable for cURL API. Here's my code so far.
state: {
posts: [],
cats: [],
},
actions: {
async fetch({ commit }) {
try {
const data = await axios.get(
"http://localhost:8080/wp-json/wp/v2/posts"
);
const data2 = await axios.get(
"http://localhost:8080/wp-json/wp/v2/categories"
);
commit("SET_posts", data.data);
commit("SET_cats", data2.data);
} catch (error) {
alert(error);
console.log(error);
}
},
},
mutations: {
SET_posts(state, posts) {
state.posts = posts;
},
SET_cats(state, cats) {
state.cats = cats;
},
},
And the API call example that deletes a post
curl -X DELETE https://example.com/wp-json/wp/v2/posts/<id>
I guess I have to create another function with at least one argument/parameter since we have to pass the id to make the api call work.
It's the first time I'm using Vuex and the first time I'm using API, sorry if what I'm asking for is obvious...
Add a new action:
actions: {
async fetch({ commit }) {
...
},
async deletePost({ dispatch }, postId) {
await axios.delete(
`http://localhost:8080/wp-json/wp/v2/posts/${postId}`
);
// fetching posts again after delete is completed
await dispatch('fetch');
},
}

Store Ability in Express Session?

I have seen the express example, where an ability is stored via middleware in the req object. It then uses the following method to evaluate the permissions:
ForbiddenError.from(req.ability).throwUnlessCan('read', article);
I want to achieve a similar thing. My idea is to save the ability inside an express session that is shared with socket io websockets. Through the sharing req.session = socket.handshake.session. My approach is the following, I make a request from the frontend application to get rules to update the ability on the frontend. The backend saves the ability inside the express session:
// abilities.js file
import { Ability } from '#casl/ability';
export const defineAbilitiesFor = (rules) => {
return new Ability(rules);
};
export default defineAbilitiesFor;
// handler for express route to get permissions from the frontend
export const getPermissions = async (req, res) => {
...
rules.push({
action: ['view'],
subject: views,
});
// manage all own processes
rules.push({
action: ['manage'],
subject: 'Process',
conditions: {
userId: req.kauth.grant.access_token.content.sub,
},
});
// store ability in session
req.session.rules = defineAbilitiesFor(rules);
const token = jwt.sign({ token: packRules(rules) }, 'secret');
if (token) {
return res.status(200).json(token);
} else {
return res.status(400).json('Error');
}
...
Then when a websocket request happens, I want to check in the backend if the user has the permissions to do that action:
ForbiddenError.from(socket.handshake.session.rules).throwUnlessCan('view', 'Process');
However, this throws the following error:
TypeError: this.ability.relevantRuleFor is not a function
at ForbiddenError.throwUnlessCan
The session object seems to have the correct ability object. When I console.log socket.handshake.session.rules, I get the following output:
{
h: false,
l: {},
p: {},
'$': [
{ action: [Array], subject: 'Process', conditions: [Object] },
{ action: [Array], subject: [Array] },
{ action: [Array], subject: 'Process', conditions: [Object] }
],
m: {}
}
Also the can function and everything else I tried wasn't working. I think storing the plain rules as an object inside the session and then updating the ability class before each request would work, but I don't want to do that. I want to store the ability right inside the session, so that I only have to execute the throwUnlessCan or can functions.
Is this even possible and if so, how would you do this?
Thanks so far.
Instead of storing the whole Ability instance, you need to store only its rules! rules is a plain js array of objects, so it can be easily serialized.So, change the code to this:
export const getPermissions = async (req, res) => {
...
rules.push({
action: ['view'],
subject: views,
});
// manage all own processes
rules.push({
action: ['manage'],
subject: 'Process',
conditions: {
userId: req.kauth.grant.access_token.content.sub,
},
});
// store ability RULES in session
req.session.rules = rules;
const token = jwt.sign({
token: packRules(rules) // packRules accepts an array of RawRule! not an Ability instance
}, 'secret');
if (token) {
return res.status(200).json(token);
} else {
return res.status(400).json('Error');
}
To use Ability in other handlers add a middleware:
function defineAbility(req, res, next) {
if (req.session.rules) {
req.ability = new Ability(req.session.rules);
next();
} else {
// handle case when there is no rules in session yet
}
}
// later
app.get('/api/users', defineAbility, (req, res) => {
req.ability.can(...);
// or
ForbiddenError.from(req.ability).throwUnlessCan(...);
})

Validate request body separately from request as a whole

I have a question for validating a PUT request. The body of the request is an array of objects. I want the request to succeed if the body contains an array of at least length one, but I also need to do a separate validation on each object in the array and pass that back in the response. So my put body would be:
[1, 2, {id: "thirdObject"}]
The response should be 200 even though the first two items are not even objects. The request just needs to succeed if an array of length 1 is passed in the body. The response needs to be something like:
[{id: firstObject, status: 400, error: should be object}, {id: secondObject, status: 400, error: should be object}, { id: thirdObject, status: 204 }]
Currently I am validating the body as such with fluent schema:
body: S.array().items(myObjectSchema)
.minItems(1)
Which will result in a 400 if any of the items in the body don’t match the myObjectSchema. Was wondering if you have any idea how to achieve this?
The validation doesn't tell you if a schema is successful (eg { id: thirdObject, status: 204 }), so you need to manage it by yourself.
To do that, you need to create an error handler to read the validation error and merge with the request body:
const fastify = require('fastify')()
const S = require('fluent-schema')
fastify.put('/', {
handler: () => { /** this will never executed if the schema validation fail */ },
schema: {
body: S.array().items(S.object()).minItems(1)
}
})
const errorHandler = (error, request, reply) => {
const { validation, validationContext } = error
// check if we have a validation error
if (validation) {
// here the validation error
console.log(validation)
// here the body
console.log(request.body)
reply.send(validation)
} else {
reply.send(error)
}
}
fastify.setErrorHandler(errorHandler)
fastify.inject({
method: 'PUT',
url: '/',
payload: [1, 2, { id: 'thirdObject' }]
}, (_, res) => {
console.log(res.json())
})
This will log:
[
{
keyword: 'type',
dataPath: '[0]',
schemaPath: '#/items/type',
params: { type: 'object' },
message: 'should be object'
},
{
keyword: 'type',
dataPath: '[1]',
schemaPath: '#/items/type',
params: { type: 'object' },
message: 'should be object'
}
]
[ 1, 2, { id: 'thirdObject' } ]
As you can see, thanks to validation[].dataPath you are able to understand which elements of the body array is not valid and merge the data to return your info.
Consider that the handler will be not executed in this scenario. If you need to execute it regardless the validation, you should do the validation job in a preHandler hook and avoid the default schema validation checks (since it is blocking)
edit
const fastify = require('fastify')()
const S = require('fluent-schema')
let bodyValidator
fastify.decorateRequest('hasError', function () {
if (!bodyValidator) {
bodyValidator = fastify.schemaCompiler(S.array().items(S.object()).minItems(1).valueOf())
}
const valid = bodyValidator(this.body)
if (!valid) {
return bodyValidator.errors
}
return true
})
fastify.addHook('preHandler', (request, reply, done) => {
const errors = request.hasError()
if (errors) {
console.log(errors)
// show the same errors as before
// you can merge here or set request.errors = errors to let the handler read them
reply.send('here merge errors and request.body')
return
}
done() // needed to continue if you don't reply.send
})
fastify.put('/', { schema: { body: S.array() } }, (req, reply) => {
console.log('handler')
reply.send('handler')
})
fastify.inject({
method: 'PUT',
url: '/',
payload: [1, 2, { id: 'thirdObject' }]
}, (_, res) => {
console.log(res.json())
})
I don't know the schema syntax you are using, but using draft 7 of the JSON Schema (https://json-schema.org/specification-links.html, and see also https://json-schema.org/understanding-json-schema for some reference material), you can do:
{
"type": "array",
"minItems": 1
}
If you want to ensure that at least one, but not necessarily all items match your object type, then add the "contains" keyword:
{
...,
"contains": ... reference to your object schema here
}

Redux-offline : commit action never called

I'am trying to implement Redux-offline in my react-native app for this i have installed the module and i have added offline to my createStore method :
const store = createStore(
myReducer,
compose(
offline(offlineConfig),
applyMiddleware(thunk, promiseMiddleware())
)
);
this is the action that uses redux-offline :
export const addResources = resource => ({
type: "ADD_RESOURCES",
payload: resource,
meta: {
offline: {
// the network action to execute:
effect: {
url: "https://reqres.in/api/users",
method: "POST",
json: {
body: { name: "paul rudd", movies: ["Fake data", "Fake text"] }
}
},
// action to dispatch when effect succeeds:
commit: { type: "FOLLOW_ADD_RESOURCE", meta: { resource } },
// action to dispatch if network action fails permanently:
rollback: { type: "ADD_RESOURCE_ROLLBACK", meta: { resource } }
}
}
});
for the sake of explanation i'am using a sample dummy API that accepts the creation of new users and return an id as a response.
My problem is that the commit action never gets called after dispatching my ADD_RESOURCES action, on the other hand therollback gets called if i'am sending a bad request.
This is my reducer:
let tempList = state.list.concat();
switch (action.type) {
case ADD_RESOURCES:
console.log("add resources action");
return Object.assign({}, state, {
list: tempList
});
case FOLLOW_ADD_RESOURCE:
console.log(" *** FOLLOW_ADD_RESOURCE **", res);
return Object.assign({}, state, {
list: tempList
});
case ADD_RESOURCE_ROLLBACK:
console.log("ADD_RESOURCE_ROLLBACK");
return Object.assign({}, state, {
list: tempList
});
default:
return state;
}
PS: i'am testing this on Pixel 2 xl API 27 emulator, with and without wifi and 3G internet connection.
As i said the commit action never get dispatched, does anyone know what did i get wrong ?

Calling two mutations from the same action

I'm using a Vuex store to keep all the items in a shopping cart.
There's two actions on the store :
getCartContent, which gets called on page load (fetches the initial content from the backend, which in turn retrieves the data from the session)
addToCart, which is dispatched by the <Products> component when the user clicks the add to cart button.
Both of these call a respective mutation (with the same name), since you're not supposed to call mutations directly from within components.
Here is what the store looks like :
const store = new Vuex.Store({
state: {
items: [],
},
mutations: {
getCartContent(state, data){
axios.get('/api/cart').then(response => {
state.items = response.data;
});
},
addToCart(state, data){
axios.post('/api/cart/add', {
item_id: data.item,
});
}
},
actions: {
getCartContent(context){
context.commit('getCartContent');
},
addToCart(context, data){
context.commit('addToCart', {item: data.item});
}
}
});
This is working as expected, but now when an item is added to the cart (with a dispatch to the addToCart action from within the component), I would like it to call the getCartContent mutation just after so that it fetches a fresh list of items from the backend.
I tried commiting the second mutation from the same action, like this :
actions: {
// ...
addToCart(context, data){
context.commit('addToCart', {item: data.item});
context.commit('getCartContent');
}
}
But that doesn't always work, sometimes it will fetch the items but not always.
I also tried dispatching the getCartContent action from within the component itself, right after dispatching the addToCart action, but it's the same problem.
How can I solve this?
Your axios calls are asynchronous, meaning that your addToCart mutation might not necessarily be finished when your getCartContent mutation fires. So, it's not surprising that sometimes getCartContent doesn't return the items you told axios to send a post request for immediately prior.
You should move asynchronous calls to the vuex actions:
actions: {
getCartContent(context, data) {
axios.get('/api/cart').then(response => {
state.items = response.data;
context.commit('getCartContent', response.data),
});
},
addToCart(context, data) {
axios.post('/api/cart/add', {
item_id: data.item,
}).then(() => {
context.commit('addToCart', data.item)
})
},
}
And your mutations should do nothing but make simple, straight-forward changes to the module state:
mutations: {
getCartContent(state, items) {
state.items = items;
},
addToCart(state, item) {
state.items.push(item);
}
}
The above explanation assumes that instead of making a get('/api/cart') request after each POST request, you would just keep track of items by pushing the data to the state.items property.
If however, you really want to make the GET request after adding an item, you can just get rid of the addToCart mutation and dispatch the getCartContent action after the POST request finishes:
addToCart(context, data) {
axios.post('/api/cart/add', {
item_id: data.item,
}).then(() => {
context.dispatch('getCartContent');
})
},