How to redirect some URL conditionally in the koa 2 - koa

This is what i'm thinking, pseudo code.
const myRedirect = (routePath) => {
newUrl = routePath;
if (matches condition)
newUrl = do_some_modification(routePath);
return next(newUrl);
}
const myFunc = (routePath, myRedirect) => (newUrl, middleware) => {
return (ctx, newUrl, next) => {
return middleware(ctx, newUrl, next);
}
};
How to modify it to make it work please ?

const route = async function(ctx, next){
if(shouldRedirect){
ctx.redirect('/redirect-url'); // redirect to another page
return;
}
ctx.someData = getSomeData(); // ctx.someData will be available in the next middleware
await next(); // go to next middleware
}

Related

How to get error from backend with axios?

I'm trying to display an error I recieve in my backend to the user in my JSX frontend file.
This is the initial call from frontend
dispatch(createGoal({ values }))
Goalslice, directly called from JSX:
export const createGoal = createAsyncThunk(
'goals/create',
async (goalData, thunkAPI) => {
try {
const token = thunkAPI.getState().auth.user.token
return await goalService.createGoal(goalData, token)
} catch (error) {
const message =
(error.response &&
error.response.data &&
error.response.data.message) ||
error.message ||
error.toString()
return thunkAPI.rejectWithValue(message)
}
}
)
Goalservice, directly called from goalslice:
const createGoal = async (goalData, token) => {
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
}
const response = await axios.post(API_URL, goalData, config)
return response.data
}
Goalcontroller, my backend:
const setGoal = asyncHandler(async (req, res) => {
const goals = await Goal.find({ user: req.user.id })
var count = Object.keys(goals).length
if(count >2){
res.status(400)
throw new Error('Maximum of 3 trackers per user')
}
if (!req.body.values) { //if this isnt there. check if the body is there.
res.status(400) //This is an error
throw new Error('Please add a date field') //this is express error handler
}
console.log(req.body.values.dates)
const goal = await Goal.create({
values: req.body.values.dates, //get from request body
permit: req.body.values.permits,
numpermit: req.body.values.num,
user: req.user.id,
})
res.status(200).json(goal)
})
I want to display this error:
throw new Error('Maximum of 3 trackers per user')
I tried a try/catch method, but I'm very new to this and I feel like i'm missing a very key point in how it all fits together.
This is my custom error handler if it helps:
const errorHandler = (err, req, res, next) => { //overwrite express error handler, next to handle any new req
const statusCode = res.statusCode ? res.statusCode : 500 //500 is server error. conditional
res.status(statusCode)
res.json({
message: err.message,
stack: process.env.NODE_ENV === 'production' ? null : err.stack, //gives additional info if in development mode : is else
})
}
module.exports = { //export for others to use
errorHandler,
}

NextJS API Route Returns Before Data Received?

I'm not sure what's going on here. I have set up an API route in NextJS that returns before the data has been loaded. Can anyone point out any error here please?
I have this function that calls the data from makeRequest():
export async function getVendors() {
const vendors = await makeRequest(`Vendor.json`);
console.log({ vendors });
return vendors;
}
Then the route: /api/vendors.js
export default async (req, res) => {
const response = await getVendors();
return res.json(response);
};
And this is the makeRequest function:
const makeRequest = async (url) => {
// Get Auth Header
const axiosConfig = await getHeader();
// Intercept Rate Limited API Errors & Retry
api.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
await new Promise(function (res) {
setTimeout(function () {
res();
}, 2000);
});
const originalRequest = error.config;
if (error.response.status === 401 && !originalRequest._retry) {
token[n] = null;
originalRequest._retry = true;
const refreshedHeader = await getHeader();
api.defaults.headers = refreshedHeader;
originalRequest.headers = refreshedHeader;
return Promise.resolve(api(originalRequest));
}
return Promise.reject(error);
}
);
// Call paginated API and return number of requests needed.
const getQueryCount = await api.get(url, axiosConfig).catch((error) => {
throw error;
});
const totalItems = parseInt(getQueryCount.data['#attributes'].count);
const queriesNeeded = Math.ceil(totalItems / 100);
// Loop through paginated API and push data to dataToReturn
const dataToReturn = [];
for (let i = 0; i < queriesNeeded; i++) {
setTimeout(async () => {
try {
const res = await api.get(`${url}?offset=${i * 100}`, axiosConfig);
console.log(`adding items ${i * 100} through ${(i + 1) * 100}`);
const { data } = res;
const arrayName = Object.keys(data)[1];
const selectedData = await data[arrayName];
selectedData.map((item) => {
dataToReturn.push(item);
});
if (i + 1 === queriesNeeded) {
console.log(dataToReturn);
return dataToReturn;
}
} catch (error) {
console.error(error);
}
}, 3000 * i);
}
};
The issue that I'm having is that getVendors() is returned before makeRequest() has finished getting the data.
Looks like your issue stems from your use of setTimeout. You're trying to return the data from inside the setTimeout call, and this won't work for a few reasons. So in this answer, I'll go over why I think it's not working as well as a potential solution for you.
setTimeout and the event loop
Take a look at this code snippet, what do you think will happen?
console.log('start')
setTimeout(() => console.log('timeout'), 1000)
console.log('end')
When you use setTimeout, the inner code is pulled out of the current event loop to run later. That's why end is logged before the timeout.
So when you use setTimeout to return the data, the function has already ended before the code inside the timeout even starts.
If you're new to the event loop, here's a really great talk: https://youtu.be/cCOL7MC4Pl0
returning inside setTimeout
However, there's another fundamental problem here. And it's that data returned inside of the setTimeout is the return value of the setTimeout function, not your parent function. Try running this, what do you think will happen?
const foo = () => {
setTimeout(() => {
return 'foo timeout'
}, 1000)
}
const bar = () => {
setTimeout(() => {
return 'bar timeout'
}, 1000)
return 'bar'
}
console.log(foo())
console.log(bar())
This is a result of a) the event loop mentioned above, and b) inside of the setTimeout, you're creating a new function with a new scope.
The solution
If you really need the setTimeout at the end, use a Promise. With a Promise, you can use the resolve parameter to resolve the outer promise from within the setTimeout.
const foo = () => {
return new Promise((resolve) => {
setTimeout(() => resolve('foo'), 1000)
})
}
const wrapper = async () => {
const returnedValue = await foo()
console.log(returnedValue)
}
wrapper()
Quick note
Since you're calling the setTimeout inside of an async function, you will likely want to move the setTimeout into it's own function. Otherwise, you are returning a nested promise.
// don't do this
const foo = async () => {
return new Promise((resolve) => resolve(true))
}
// because then the result is a promise
const result = await foo()
const trueResult = await result()

Using a URL query parameter to version cached responses

I am trying to cache specific urls and each url has md5 hash and If the urls updated with new md5 i want to remove the current cache and add the new one.
cached url: http://www.mysite.lo/cards/index.php?md5=f51c2ef7795480ef2e0b1bd24c9e07
function shouldFetch(event) {
if ( event.request.url.indexOf( '/cards/') == -1 ) {
return false;
}
return true;
}
self.addEventListener('fetch', function(event) {
if (shouldFetch(event)) {
event.respondWith(
caches.match(event.request).then(function(response) {
if (response !== undefined) {
return response;
} else {
return fetch(event.request).then(function (response) {
let responseClone = response.clone();
caches.open('v1').then(function (cache) {
cache.put(event.request, responseClone);
});
return response;
}).catch(function (err) {
return caches.match(event.request);
});
}
})
);
}
});
I know we can use caches.delete() and so on, but I want to call it only if the md5 updated from the new request.
Thanks
You can accomplish roughly what you describe with the following, which makes use of the ignoreSearch option when calling cache.matchAll():
self.addEventListener('fetch', (event) => {
const CACHE_NAME = '...';
const url = new URL(event.request.url);
if (url.searchParams.has('md5')) {
event.respondWith((async () => {
const cache = await caches.open(CACHE_NAME);
const cachedResponses = await cache.matchAll(url.href, {
// https://developers.google.com/web/updates/2016/09/cache-query-options
ignoreSearch: true,
});
for (const cachedResponse of cachedResponses) {
// If we already have the incoming URL cached, return it.
if (cachedResponse.url === url.href) {
return cachedResponse;
}
// Otherwise, delete the out of date response.
await cache.delete(cachedResponse.url);
}
// If we've gotten this far, then there wasn't a cache match,
// and our old entries have been cleaned up.
const response = await fetch(event.request);
await cache.put(event.request, response.clone());
return response;
})());
}
// Logic for non-md5 use cases goes here.
});
(You could make things slightly more efficient by rearranging some of the cache-manipulation code to bring it out of the critical response path, but that's the basic idea.)

Implementing ACL with webhooks for VerneMQ

I am trying to get ACL behavior by implementing my own webhooks for VerneMQ. I am using express and apicache node packages for this. I hope the code is meaningful to non-javascript-programmers as well.
In my vernemq.conf I have set up my hooks, and they get called correctly:
$ vmq-admin webhooks show
+-----------------+------------------------------+-------------+
| hook | endpoint |base64payload|
+-----------------+------------------------------+-------------+
|auth_on_subscribe|http://127.0.0.1:3000/vmq/sub | true |
|auth_on_register |http://127.0.0.1:3000/vmq/auth| true |
| auth_on_publish |http://127.0.0.1:3000/vmq/pub | true |
+-----------------+------------------------------+-------------+
Also I disabled all other plugins and disabled anonymous login.
My webhooks implementation in express (simplified):
const express = require('express');
const apicache = require('apicache');
const bodyparser = require('body-parser');
// short cache times for demonstration
const authcache = apicache.middleware('15 seconds');
const pubcache = apicache.middleware('5 seconds');
const subcache = apicache.middleware('10 seconds');
const app = express();
const jsonparser = bodyparser.json();
app.use((req, res, next) => {
console.log(`${req.connection.remoteAddress}:${req.connection.remotePort} ${req.method} ${req.path}`);
return next();
});
app.post('/vmq/auth', authcache, (req, res) => {
return res.status(200).json({result: 'ok'});
});
app.post('/vmq/pub', pubcache, jsonparser, (req, res) => {
// this gets ignored most of the time because of caching
if (req.body.topic === 'only/allowed/topic') {
return res.status(200).json({result: 'ok'});
}
return res.status(401).end();
});
app.post('/vmq/sub', subcache, (req, res) => {
return res.status(200).json({result: 'ok'});
});
app.use((req, res, next) => {
return res.status(404).end();
});
app.use((err, res, req, next) => {
console.error(err);
return res.status(500).end();
});
const server = app.listen(3000, 'localhost', () => {
const address = server.address();
return console.log(`listening on ${address.address}:${address.port} ...`);
});
Using mqtt.js I wrote a client (simplified):
const mqtt = require('mqtt');
const client = mqtt.connect('mqtt://localhost');
client.on('connect', () => {
setInterval(() => {
client.publish('only/allowed/topic', 'working');
client.publish('some/disallowed/topic', 'working too :(');
}, 500);
return client.subscribe('some/disallowed/topic');
});
client.on('message', (topic, message) => {
return console.log(`${topic}:${message}`);
});
What happens is that the client successfully authenticates and then publishes to only/allowed/topic, which is allowed and gets cached as successful by VerneMQ. However, since the successful call to /vmq/pub is now cached, publishing to some/disallowed/topic also works. If I change the order of publishing, both will fail.
I would have expected VerneMQ to map the cached results to all parameters in a call, except the payload of course, and not just to a client connection. However that is not the case. What's a possible way to implement ACL via webhooks while using caching? Not using caching is out of the question, as this kills my performance, and caching is recommended by the docs anways.
Also, will someone with 1500+ rep be so nice and create the tag vernemq? :)
I misunderstood how apicache works and what it actually does. All I needed to do was just setting the appropriate header for caching, as stated in the docs of VerneMQ. Apparently apicache stores the actual result and returns that whenever asked within the specified timeframe, no matter what the client is actually requesting.
This is the working code now:
const express = require('express');
const bodyparser = require('body-parser');
const app = express();
// short cache times for demonstration (in seconds)
const authcachetime = 15;
const pubcachetime = 5;
const subcachetime = 10;
const jsonparser = bodyparser.json();
app.use((req, res, next) => {
console.log(`${req.connection.remoteAddress}:${req.connection.remotePort} ${req.method} ${req.path}`);
return next();
});
app.post('/vmq/auth', (req, res) => {
res.set('cache-control', `max-age=${authcachetime}`);
return res.status(200).json({result: 'ok'});
});
app.post('/vmq/pub', jsonparser, (req, res) => {
res.set('cache-control', `max-age=${pubcachetime}`);
if (req.body.topic === 'only/allowed/topic') {
return res.status(200).json({result: 'ok'});
}
return res.status(401).end();
});
app.post('/vmq/sub', (req, res) => {
res.set('cache-control', `max-age=${subcachetime}`);
return res.status(200).json({result: 'ok'});
});
app.use((req, res, next) => {
return res.status(404).end();
});
app.use((err, res, req, next) => {
console.error(err);
return res.status(500).end();
});
const server = app.listen(3000, 'localhost', () => {
const address = server.address();
return console.log(`listening on ${address.address}:${address.port} ...`);
});
As expected the client now gets an error when it tries to publish to an illegal topic.

Cannot POST ajax request

can anyone explain to me why i cannot post the ajax request. When i run this code the console appear POST http://localhost:8080/api/users 404 (Not Found), and in the networth part the preview is 404 not found
In the index.js file
var path = require('path');
var express = require('express');
var webpack = require('webpack');
var config = require('./webpack.config.dev.js');
var app = express();
var compiler = webpack(config);
var bodyParser = require('body-parser');
var users = require('./server/routes/users');
app.use(bodyParser.json());
app.use('/api/users', users);
app.use(require('webpack-dev-middleware')(compiler, {
noInfo: true,
publicPath: config.output.publicPath
}));
app.use(require('webpack-hot-middleware')(compiler));
app.get('*', function(req, res) {
res.sendFile(path.join(__dirname, 'index.html'));
});
app.listen(8080, 'localhost', function(err) {
if (err) {
console.log(err);
return;
}
console.log('Listening at http://localhost:8080');
});
and the users file
var express = require('express')
var router = express.Router()
// middleware that is specific to this router
function validateInput(data) {
let errors = {};
if (Validator.isNull(data.username)){
errors.username = "This field is required";
}
if (Validator.isEmail(data.email)) {
errors.email = "Email is invalid";
}
if (Validator.isNull(data.password)){
errors.password = 'This field is required';
}
if (Validator.isNull(data.passwordConfirmation)){
errors.passwordConfirmation = 'This field is required';
}
if (Validator.equals(data.password, data.passwordConfirmation)){
errors.passwordConfirmation = 'Password must match';
}
return {
errors,
isValid: isEmpty(errors)
}
}
router.post('/api/users', (req, res) => {
console.log('runiing the router/post');
console.log(req.body);
const {errors, isValid} = validateInput(req.body);
if (!isValid) {
res.status(400).json(errors);
}
});
module.exports = router
in users.js file the function should be
router.post('/', (req, res) => {
console.log('runiing the router/post');
console.log(req.body);
const {errors, isValid} = validateInput(req.body);
if (!isValid) {
res.status(400).json(errors);
}
});
Because index.js have already resolved /api/users part in the request url http://localhost:8080/api/users at this point. So you only have to map after the /api/users in your users js file.
For example, if you have following function in users.js file
router.post('/:id', (req, res) => {
...
}
It will resole to the path http://localhost:8080/api/users/1
Edit
In your existing version,
router.post('/api/users', (req, res) => {
...
}
will resolve as http://localhost:8080/api/users/api/users

Resources