i guys I am currently using nodejs with express session and connect session sequelize.
Currently I am trying to pass my userID from session and to be accessed at another add product page. So that the database can identify based on userID. I am getting req.session.users is not a function.
Below is my code.
Sessions login page:
const User = require('../models/user')
exports.postLoginPage = (req,res,next) =>{
User.findByPk(1)
.then(users =>{
req.session.isLoggedIn = true;
req.session.users = users;
res.redirect('/');
})
.catch(err =>{
console.log(err)
})
}
the addProduct Page
exports.postaddProductsMale = (req,res,next) =>{
const title = req.body.title;
const imageUrl = req.body.imageUrl;
const price = req.body.price;
console.log(+req.session.users)
req.session.users.createProduct({
title:title,
imageUrl:imageUrl,
price:price
})
.then(results =>{
console.log('Product created')
res.redirect('male_section');
})
.catch(err =>{
console.log(err)
})
};
this is my error :
TypeError: req.session.users.createProduct is not a function
at exports.postaddProductsMale (C:\Users\ASUS\Desktop\Hobby\projects\javascript\ecommerce clothing\maarancode\controllers\admin.js:28:23
I can get the data to the other routes using req.user not sure why req.session.users is not working.
Appreciate your feedback on this question. Thank you.
req.session must be serializable so that it can be stored between requests, see for example here. But a function like req.session.users.createProduct is not serializable, so it may not survive until the next request.
See the following example
express()
.use(session({
secret: "Se$$ion",
resave: false,
saveUninitialized: false
}))
.use("/test", function(req, res) {
if (!req.session.mybool) {
req.session.mybool = true;
req.session.myfunc = function() {};
}
res.end(typeof(req.session.mybool) + " " + typeof(req.session.myfunc));
})
.listen(80);
If you make two requests, the first response is boolean function but the second is boolean undefined.
Related
I want to create A post route So I Can store the User Typed code snippets into the collection in MongoDB.
the schema would look like this:-
const newSnippetSchema= new mongoose.Schema({
title:String,
snippet:String
})
to be brief I am working on creating a web app like codeSandbox or code-pen where I can save the code user has saved or typed....
I want to send data in Json format when Post Route is triggered
Create a http post web api.
const express = require('express')
const mongoose = require('mongoose')
const NewSnippetSchema = require('./models/newSnippetSchema')
const app = express()
mongoose.connect('mongodb://localhost/mydb', {
useNewUrlParser: true, useUnifiedTopology: true
})
app.set('view engine', 'ejs')
app.use(express.urlencoded({ extended:false }))
app.post('/newSnippet', async (req, res) => {
await NewSnippetSchema.create({ title: req.body.title, snippet: req.body.snippet })
res.redirect('/')
})
app.listen(process.env.PORT || 5000);
catchAsync Code:
const catchAsync = (fn) =>
function asyncUtilWrap(...args) {
const fnReturn = fn(...args);
const next = args[args.length - 1];
return Promise.resolve(fnReturn).catch(next);
};
export default catchAsync;
AppError Code:
class AppError extends Error {
constructor(message, statusCode) {
super(message);
this.statusCode = statusCode;
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
this.isOperational = true;
Error.captureStackTrace(this, this.constructor);
}
}
export default AppError;
Controller Code:
const snippetController = catchAsync(async (req, res, next) => {
const { title, snippet } = req.body;
if (!title || !snippt) {
return next(new AppError('All fields are required', 400));
}
const snippetDoc = await Snippet.create(req.body);
return res.status(201).json({
status: 'success',
snippetDoc
});
});
export default snippetController;
Router Code:
router.route('/snippet').post(snippetController);
The database has nothing to do with this. It doesn't matter how you store it, what matters is how render it in your HTML representation on the UI.
Just use the built-in HTML encoding function to encode the code snippet before storing it in the database.(eg - encode all & as & something like this).
After fetching the data, decode it back before rendering it on the UI.
I have a list of articles that have a property views and I want to increment that property in the database each time a user clicks on an article title. Currently nothing happens when I do it. Why isn't it working and how can I increment that property each time on click? Here is my React part:
const incrementViews = (id) => {
var item = posts.find(x => x._id === id);
item.views += 1;
}
<div className="post-title">
<Link to={`/post/${post._id}`}>
<h2><a href="#" onClick={() => incrementViews(post._id)}>{post.title}</a>
</h2>
</Link>
</div>
and my server.js:
// Requiring the dependencies
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cors = require('cors');
require('dotenv').config();
const mongoose = require('mongoose');
const PORT = process.env.PORT || 3001;
const BASE_URL = process.env.REACT_APP_BASE_URL;
console.log(BASE_URL)
const itemRoutes = express.Router();
let Comment = require('./comment.model');
app.use(cors());
app.use(bodyParser.json());
mongoose.connect(BASE_URL, { useNewUrlParser: true })
const connection = mongoose.connection;
connection.once('open', function () {
console.log('Connection to MongoDB established succesfully!');
});
let collection = connection.collection("posts_with_tags_test");
collection.createIndex(
{
postContent: 'text',
title: 'text'
}
);
// Serve static assets
if (process.env.NODE_ENV === 'production') {
app.use(express.static('build'));
}
itemRoutes.route('/').get(async (req, res) => {
let collection = connection.collection("posts_with_tags_test");
let response = await collection.find({})
.toArray();
res.send(response);
});
itemRoutes.route('/search').post(async (req, res) => {
let result = await connection.collection("posts_with_tags_test").find({
$text: {
$search: req.body.searchString
}
}).toArray();
res.send(result);
});
itemRoutes.route("increment/:id"").post(async (req, res) => {
const { id } = req.params;
collection.updateOne({ _id: id }, { $inc: { views: 1 } });
return res.status(200).json({ msg: "OK" });
});
itemRoutes.route('/comments').get(async (req, res) => {
let collection = connection.collection("comments");
let response = await collection.find({})
.toArray();
res.send(response);
});
itemRoutes.route('/comments')
.post((req, res) => {
res.setHeader('Content-Type', 'application/json');
let comment = new Comment(req.body);
comment.save()
.then(comment => {
res.status(200).json({ comment })
})
.catch(err => {
res.status(400).send('failed')
})
});
app.use('/', itemRoutes);
app.use('/comments', itemRoutes);
app.use('/search', itemRoutes);
app.use('/increment', itemRoutes);
app.listen(PORT, function () {
console.log('Server is running on' + ' ' + PORT);
})
I think there are two problems in frontend and backend respectively.
Front-end
You should use post variable as a state variable so as to re-render then component when changes are made on post.
Back-end
There is no issue with increasing view in your code.
Here, you need to return success status.
The function incrementViews only increments views on the frontend and never sends any data to the API. One way you can make it work is as follows:
server.js
itemRoutes.route("/increment/:id").post(async (req, res) => {
const { id } = req.params;
collection.updateOne({ _id: id }, { $inc: { views: 1 } });
return res.status(200).json({ msg: "OK" });
});
React
const incrementViews = (id) => {
// Assuming your API server is running on port 5000.
fetch(`http://localhost:5000/increment/${id}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
})
.then((res) => res.json())
.then(console.log)
.catch(console.error);
};
Update
The reason you're getting 404 is a missing colon : in the route parameters.
// Notice the :id, colon is important.
itemRoutes.route("/increment/:id").post(async (req, res) => {
const { id } = req.params;
// ...
});
Here is a demo reproduced on Glitch. Removed database logic and just added a response messages.
I tested the demo using Postman and it works fine.
On a POST request to https://adaptive-sassy-legal.glitch.me/increment/123, should return a response as shown below.
{
msg: "itemsRoute increment.",
id: "123"
}
Update 2
Another thing which I forgot to mention in the previous update was to update the middleware.
// Use only `/increment` instead of `increment/:id`.
app.use("/increment", itemRoutes);
Here is an updated demo.
i wrote a login page code in js that runs on node express.js server and anyone can put their username /email and password and all that data goes into an json file and it looks like this
{"username":"admin","password":"pasword","email":"user#stackoverflow.com","timestamp":1598668572045,"_id":"dx8HqKkVWe8olH5z"}
i managed to get the timestamp and NeDB gives a random _id to that object.
and when you login you go to a home page that looks like this
but the user gets the username value when there is on object only on the database which is "database.json"
if there is more than 1 object on the database that action will crash and the client can't see his name or any thing it shows nothing .
i don't know how to make it work with several objects on the database.
i thought JSON.parse Or stringfy could make it work but i don't know how to use them on my case.
so here is the js code
var jsonn = '/database.json';
async function userinfo() {
const response = await fetch(jsonn);
const data = await response.json();
var { username, password } = data;
document.getElementById('user').textContent = username;
console.log (data)
console.log (username);
console.log (id);
}
userinfo();
i appreciate any help, if you got any idea please share it with me i really need your help.
UPDATE :
the error message says :
uncaught (in promise) syntaxError : unxpected token in json positon 126.
my server.js code :
const Datastore = require('nedb');
app.listen(2000, () => console.log('listening at 3000'));
app.use(express.static('/public'));
app.use(express.json({
limit: '1mb'
}));
const database = new Datastore('public/database.json');
database.loadDatabase();
app.post('/public', (request, response) => {
const data = request.body;
const timestamp = Date.now();
data.timestamp = timestamp;
database.insert(data);
response.json(data);
console.log(data);
var logdate = new Date;
console.log(logdate);
});
There were some issues with the way that you are calling the DB insert. Basically, on every post request, you allow an insert. This is causing you to have multiple users with the same username. When you search for users by username then you will get a bunch of users. Instead, you want something like the sample code below.
I removed the status public to make it easier to test so make sure to add it back in so you can test front end code. Right now there's just a GET request endpoint so you can get the data by username through a query. This requires more cleanup and checks but at least it will get you started. Also, I remove the password and DB _id from the response as this is probs data you don't want to send back. Ideally, you will encrypt the password before storing it in DB.
const express = require('express');
const app = express();
const Datastore = require('nedb');
app.listen(3000, () => console.log('listening at 3000'));
app.use(express.json({
limit: '1mb'
}));
const database = new Datastore('public/database.json');
database.loadDatabase();
app.get('/public', (req, res) => {
const { username } = req.query;
database.findOne({
username,
}, (err, user) => {
if(err) {
return res.sendStatus(500);
}
delete user._id;
delete user.password;
return res.json(user);
});
});
app.post('/public', (req, res) => {
const data = req.body;
const {
username
} = data;
database.findOne({
username,
}, (err, user) => {
if(err) {
return res.sendStatus(500);
}
if(user) {
delete newUser._id;
delete newUser.password;
return res.json(user)
}
data.timestamp = Date.now();
database.insert(data, (createError, newUser) => {
if(createError) {
return res.sendStatus(500);
}
delete newUser._id;
delete newUser.password;
res.json(newUser);
});
});
});
I made it to add an app with an Add to Slack Button using Bolt for Javascript. The Slack commands don't work yet, because I don't have a database with auth tokens yet. I planned to implement that but realized that I never see the console log.
So from my understanding the console.log("authorizeFn") should work
const { App, ExpressReceiver } = require("#slack/bolt");;
const expressReceiver = new ExpressReceiver({
signingSecret: process.env.SLACK_SIGNING_SECRET,
endpoints: "/events"
});
const authorizeFn = async ({ teamId }) => {
//Implement query of database looking for teamId, getting auth token...
console.log("authorizeFn") //This one never logs???
};
const app = new App({
authorize: authorizeFn,
receiver: expressReceiver
});
const app_express = expressReceiver.app;
It should check with every event if the user is authorized, correct?
The code goes on like that
/* Page with add button, can be implemented in website instead */
app_express.get("/auth/add", (req, res, next) => {
res.write(
'<a href="https:/...'
);
res.end();
});
app_express.get("/auth/direct", (req, res, next) => {
res.redirect(
"https://slack...."
);
res.end();
});
/* Thanks for installing! */
app_express.get("/auth/thanks", (req, res, next) => {
res.write("Thanks for installing!");
res.end();
});
/* oauth callback function */
app_express.get("/auth/callback", (req, res, next) => {
let code = req.query.code;
let state = req.query.state;
return app.client.oauth.v2
.access({
client_id: process.env.SLACK_CLIENT_ID,
client_secret: process.env.SLACK_CLIENT_SECRET,
code: code
})
.then(async result => {
console.log(result);
// save result of oauth.access call somewhere, like in a database.
res.redirect(process.env.BASE_DOMAIN + "/auth/thanks");
res.end();
})
.catch(error => {
throw error;
});
});
console.log(result); logs something useful, which looke like teamIds, Users and a token
it had to be
const expressReceiver = new ExpressReceiver({
signingSecret: process.env.SLACK_SIGNING_SECRET,
endpoints: "/slack/events"
});
in my case. Not:
endpoints: "/events"
In order to secure REST API I'm using middleware to check for user's JWT token and only allow that particular user to access his own data.
In auth.js
const jwt = require('jsonwebtoken')
const User = require('../models/user')
const auth = async (req, res, next) => {
try {
const token = req.header('Authorization').replace('Bearer ', '')
const decoded = jwt.verify(token, process.env.JWT_SECRET)
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token })
if (!user) { // If no user is found
throw new Error()
}
// if there's a user
req.token = token
req.user = user
next()
} catch (e) {
res.status(401).send({ error: 'Please authenticate.' })
}
}
module.exports = auth
In one of the get/update router
router.get('/itemShipmentStatus', auth, async (req, res) => {
// Get the items shipment status from db.
})
However, I've noticed I need to create a new admin user (e.g. admin 1, admin2) to get and update the itemShipmentStatus for all the users. Is there a way to achieve user group authentication through the middleware (auth.js?)
Update:
The only solution I can think of is to add another "userGroup" field to the user document when creating a new user. Then in the middleware auth.js add in another condition to check if the user belongs to the admin group.
if (!user || user.userGroup !== 'Admin') { // If no user is found
throw new Error()
}
Is this the conventional way of doing it?
I would suggest adding permissions array stored in the user. That way you are more flexible. Then
const auth = (allowedPermission) => (async (req, res, next) => {
try {
const token = req.header('Authorization').replace('Bearer ', '')
const decoded = jwt.verify(token, process.env.JWT_SECRET)
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token })
if (!user) { // If no user is found
throw new Error()
}
if (!user.permissions.includes(allowedPermission)){
throw new Error() // Forbidden 403
}
// if there's a user
req.token = token
req.user = user
next()
} catch (e) {
res.status(401).send({ error: 'Please authenticate.' })
}
})
and in the route
router.get('/itemShipmentStatus', auth([admin, user]), async (req, res) => {
// Get the items shipment status from db.
})
Then it would be a matter to identify the correct code to run.
I would suggest considering the division of the api. A public api and an admin api. This is because conceptually a user may want to be admin and access its own itemShipmentStatus. So having
router.get('/itemShipmentStatus', auth([admin, user]), async (req, res) => {
// Get the items shipment status from db.
})
router.get('/admin/itemShipmentStatus', auth([admin]), async (req, res) => {
// Get the items shipment status from db of all user.
})
This allows an admin user to test the API as a normal user and get all the status as an admin.
A more conventional way of doing this would be to create an AuthRouter which extends the default express.Router and checks for allowed roles, so there will be no need to use middleware for each route.
Extending express.Router to check for roles:
const express = require('express');
const jwt = require('jsonwebtoken');
const User = require('../models/user');
export default class AuthRouter extends express.Router {
addRoles(...roles) {
return this.use(checkAccessForRoles(...roles));
}
}
const checkAccessForRoles = (...roles) => async (req, res, next) => {
const token = req.header('Authorization').replace('Bearer ', '');
const decoded = jwt.verify(token, process.env.JWT_SECRET);
const user = await User.findOne({ _id: decoded._id, 'tokens.token': token });
if (!roles.includes(user.role)) throw new Error('Forbidden');
req.user = user;
return next();
};
Using AuthRouter for ADMIN user role:
const adminRouter = new AuthRouter({
prefix: '/admin',
})
.addRoles(['ADMIN']);
adminRouter.get('/itemShipmentStatus', async (req, res) => {
// Get the items shipment status from db.
});