I'm new to the MEAN stack and I'm having some trouble with routing...
I have a module called "applications".
the APIs i want on the server side are:
get: http://localhost:3000/api/applications/(_appid)
getByMakeathonId: http://localhost:3000/api/applications/makeathons/(_mkid)
Applications Service
function ApplicationsService($resource) {
return $resource('api/applications/:path/:applicationId', {
path: '#path',
applicationId: '#id'
}, {
get: {
method: 'GET',
params: {
path: '',
applicationId: '#_id'
}
},
getByMakeathonId: {
method: 'GET',
params: {
path: 'makeathon',
applicationId: '#_id'
}
},
update: {
method: 'PUT'
}
});
Server Routing
app.route('/api/applications').post(applications.create);
app.route('/api/applications').all(applicationsPolicy.isAllowed)
.get(applications.list);
app.route('/api/applications/makeathon/:makeathonId').all(applicationsPolicy.isA llowed)
.get(applications.applicationByMakeathonID);
1) what I'm getting when I call $save and the object and the save is successful, there is a call for .get, and the request URL is: http://localhost:3000/api/applications//56f15736073083e00e86e170 (404 not found)
the problem here of course is the extra '/' - How do I get rid of it.
2) when I call getByMakeathonId, the request url is: http://localhost:3000/api/applications/makeathon?id=56e979f1c6687c082ef52656 400 (Bad Request)
I do I configure so that I'll get the two requests that I want?
10x!
You are getting the repeated // in your request url because you have declared that there will be a :path in your applications resource, and you are providing an empty string to interpolate there.
As $resource is meant to provide RESTful interaction with your API, I think the most appropriate approach would be to have separate $resources to deal with applications and makeathons. Something like this:
For applications:
function ApplicationsService($resource) {
return $resource('api/applications/:applicationId', {
applicationId: '#id'
}, {
update: {
method: 'PUT'
}
});
}
For makeathons:
function MakeathonsService($resource) {
return $resource('api/applications/makeathons/:makeathonId', {
makeathonId: '#id'
}
});
}
/** your server route would then be updated to
* app.route('/api/applications/makeathons/:makeathonId')...
*/
Related
I've created a function to get a user's IP address and save to a database, but instead of saving to the database, my home page is quickly and infinitely refreshing. I've tried removing watchers that may be causing this, but this did not solve the problem.
All the relevant parts that are triggering this loop:
1 - When home is mounted:
mounted() {
document.documentElement.scrollTop = 0;
IpService.getIpAddress().then((data) => {
const ip = localStorage.getItem("ip");
this.ip = data.data.ip;
if (!ip) {
VisitorsService.add(this.ip).then(()=> {
localStorage.setItem("ip",this.ip);
})
}
});
},
2 - ip service to get user's ip:
export default class ipService {
static getIpAddress() {
return axios.get("https://www.cloudflare.com/cdn-cgi/trace", {
responseType: "text",
transformResponse: data =>
Object.fromEntries(data.trim().split("\n").map(line => line.split("=")))
})
}
}
3 - This is the part where is happening the issue, if I remove this the problem stops, but it's necessary that this method works
static add(ip) {
return axios({
url: APIConstants.baseURL + "visitors/add",
method: "POST",
data: {
ip: ip
}
});
}
Conclusion: I have no idea why adding a simple axios function is causing an infinite loop on my home page. If I remove that function the issue stop. I would like to know if anyone has an idea on how can I fix this terrible loop?
Trying first stackover solution:
A user suggested to use everything async it might solve the issue:
1 - mounted at home
async mounted() {
document.documentElement.scrollTop = 0;
let data = await IpService.getIpAddress();
this.ip = data.data.ip;
//The issue happens only after this add()
await VisitorsService.add(this.ip);
},
2 - ipService class
const axios = require('axios');
export default class ipService {
static async getIpAddress() {
return await axios.get("https://www.cloudflare.com/cdn-cgi/trace", {
responseType: "text",
transformResponse: data =>
Object.fromEntries(data.trim().split("\n").map(line => line.split("=")))
})
}
}
3 - Visitor service where the is the issue:
static async add(ip) {
return axios({
url: APIConstants.baseURL + "visitors/add",
method: "POST",
data: {
ip: ip
}
});
}
New conclusion changing everything to async still keep the loop problem.
It's a re-rendering issue ,the user who posted this topic had a similar issue
Infinite loop on axios call, React
You should probably always use an async function and await request calls
export default class ipService = async () => {
static getIpAddress() {
return await axios.get("https://www.cloudflare.com/cdn-cgi/trace", {
responseType: "text",
transformResponse: data =>
Object.fromEntries(data.trim().split("\n").map(line => line.split("=")))
})
}
}
I want to send a POST request to an external API with axios in a nuxt projekt where I use the nuxt auth module.
When a user is authenticated axios seems to automatically add an authorization header (which is fine and often required for calls to my backend API). However, when doing calls to an external API the header might not be accepted and cause the call to fail.
Is there any way to specify for which URLs the auth header should be added or excluded?
Here are the configurations of the auth and axios module in my nuxt.config
// Axios module configuration
axios: {
baseURL: '//localhost:5000',
},
// Auth module configuration
auth: {
strategies: {
local: {
endpoints: {
login: { url: '/auth/login', method: 'post', propertyName: 'token' },
logout: { url: '/auth/logout', method: 'delete' },
user: { url: '/auth/user', method: 'get', propertyName: 'user' },
},
},
},
}
Some more background:
In my particular usecase I want to upload a file to an Amazon S3 bucket, so I create a presigned upload request and then want to upload the file directly into the bucket. This works perfectly fine as long as the user is not authenticated.
const { data } = await this.$axios.get('/store/upload-request', {
params: { type: imageFile.type },
})
const { url, fields } = data
const formData = new FormData()
for (const [field, value] of Object.entries(fields)) {
formData.append(field, value)
}
formData.append('file', imageFile)
await this.$axios.post(url, formData)
I tried to unset the Auth header via the request config:
const config = {
transformRequest: (data, headers) => {
delete headers.common.Authorization
}
}
await this.$axios.post(url, formData, config)
This seems to prevent all formData related headers to be added. Also setting any header in the config via the headers property or in the transformRequest function does not work, which again causes the call to the external API to fail obviously (The request will be sent without any of these specific headers).
As I'm working with the nuxt axios module I'm not sure how to add an interceptor to the axios instance as described here or here.
Any help or hints on where to find help is very much appreciated :)
Try the following
Solution 1, create a new axios instance in your plugins folder:
export default function ({ $axios }, inject) {
// Create a custom axios instance
const api = $axios.create({
headers: {
// headers you need
}
})
// Inject to context as $api
inject('api', api)
}
Declare this plugin in nuxt.config.js, then you can send your request :
this.$api.$put(...)
Solution 2, declare axios as a plugin in plugins/axios.js and set the hearders according to the request url:
export default function({ $axios, redirect, app }) {
const apiS3BaseUrl = // Your s3 base url here
$axios.onRequest(config => {
if (config.url.includes(apiS3BaseUrl) {
setToken(false)
// Or delete $axios.defaults.headers.common['Authorization']
} else {
// Your current axios config here
}
});
}
Declare this plugin in nuxt.config.js
Personally I use the first solution, it doesn't matter if someday the s3 url changes.
Here is the doc
You can pass the below configuration to nuxt-auth. Beware, those plugins are not related to the root configuration, but related to the nuxt-auth package.
nuxt.config.js
auth: {
redirect: {
login: '/login',
home: '/',
logout: '/login',
callback: false,
},
strategies: {
...
},
plugins: ['~/plugins/config-file-for-nuxt-auth.js'],
},
Then, create a plugin file that will serve as configuration for #nuxt/auth (you need to have #nuxt/axios installed of course.
PS: in this file, exampleBaseUrlForAxios is used as an example to set the variable for the axios calls while using #nuxt/auth.
config-file-for-nuxt-auth.js
export default ({ $axios, $config: { exampleBaseUrlForAxios } }) => {
$axios.defaults.baseURL = exampleBaseUrlForAxios
// I guess that any usual axios configuration can be done here
}
This is the recommended way of doing things as explained in this article. Basically, you can pass runtime variables to your project when you're using this. Hence, here we are passing a EXAMPLE_BASE_URL_FOR_AXIOS variable (located in .env) and renaming it to a name that we wish to use in our project.
nuxt.config.js
export default {
publicRuntimeConfig: {
exampleBaseUrlForAxios: process.env.EXAMPLE_BASE_URL_FOR_AXIOS,
}
}
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(...);
})
I have multiple endpoints starting with /networks/{networkId}/*. I don't want to have logic to find a network and execute some extra validation on it, in every handler. Is there any way to solve this on a higher level? Ex. plugins/server method etc?
In every handler I have the following boilerplate code:
import networkRepo from 'common/repositories/network';
// handler.js
export default (req, reply) => {
return networkRepo.findById(req.params.networkId).then(network => {
// Logic to validate whether the logged user belongs to the network
// Logic where I need the network instance
});
}
The best situation would be:
// handler.js
export default (req, reply) => {
console.log(req.network); // This should be the network instance
}
Best way to achieve what you want is to either create a generic function that you can call first inside your handler or alternatively create an internal hapi route which will perform you look-up and return value to your other handler. Internal routes can then be accesses by server.inject from within your other handler, see options called allowInternals for more details, I can write pseudo-code to help!
[{
method: 'GET',
path: '/getNetworkByID/{id}',
config: {
isInternal: true,
handler: function (request, reply) {
return networkRepo.findById(req.params.networkId).then(network => {
// Logic to validate whether the logged user belongs to the network
// Logic where I need the network instance
reply(network.network);
});
}
}
},
{
method: 'GET',
path: '/api/networks/{id}',
config: {
isInternal: true,
handler: function (request, reply) {
request.server.inject({
method: 'GET',
url: '/getNetworkByID/' + request.params.id,
allowInternals: true
}, (res) => {
console.log(res.result.network) //network
});
}
}
}]
I'm using Ember with Ember Data and working with a REST API that returns only a small set of properties at the list endpoint and the full payload at the details endpoint. For example:
URL: /api/users
{
"users":[
{"id":1,"name":"Bob"},
{"id":2,"name":"Sam"}
]
}
URL: /api/users/1
{
"user":{
"id":1,
"name": "Bob",
"description": "Lorem ipsum dolor"
}
}
Notice how /api/users/1 returns more properties than the list. With Ember Data, how do you get it to fetch the full payload when necessary if I have my Routes set up as follows?
App.UsersRoute = Ember.Route.extend({
model: function () {
return this.store.find('user');
}
});
App.UserRoute = Ember.Route.extend({
model: function (params) {
return this.store.find('user', params.user_id);
}
});
The problem is this line:
this.store.find('user', params.user_id)
It doensn't request the full payload from the server if the list has already be loaded, so calling the store's find method returns the user with limited properties from its cache.
You could do something like:
var user = this.store.getById('user', params.user_id);
if (user) {
return user.reload();
} else {
return this.store.find('user', params.user_id);
}
Check store methods (getById, hasRecordForId, recordIsLoaded) and model reload.