Mongodb moongose log table - javascript

I am interested in how to create log table that writes data in own table, every time user makes some request.
And how to get data like this:
{
_id: ObjectId('4f442120eb03305789000000'),
host: "127.0.0.1",
logname: null,
user: 'frank',
time: ISODate("2000-10-10T20:55:36Z"),
path: "/apache_pb.gif",
request: "GET /apache_pb.gif HTTP/1.0",
status: 200,
response_size: 2326,
referrer: "[http://www.example.com/start.html](http://www.example.com/start.html)",
user_agent: "Mozilla/4.08 [en] (Win98; I ;Nav)"
}
Maybe not all of this data, but atleast who made the request, what type of request, path and time.
I am using nodejs, mongodb, mongoose.

You can write a middleware that logs all the requests, being sent to your server, to the MongoDB Database.
You can easily get the information you are looking for, using these npm packages,
1 - useragent
2 - express-useragent

I solved on this way.
My middlewere
const Log = require("../models/log");
const log = async (req, res, next) => {
try {
let user_id = req.user.id
let firstName = req.user.firstName
let method = req.method
let path = req.path
const log = new Log({ user_id, firstName, method, path });
try {
await log.save()
} catch (e) {
res.status(400).send(e)
}
next();
} catch (e) {
res.status(401).send(e);
}
};
module.exports = log;
Model
const mongoose = require('mongoose')
const logSchema = new mongoose.Schema({
user_id: {
type: String,
},
firstName: {
type: String,
},
method: {
type: String,
},
path: {
type: String,
},
}, {
timestamps: true
})
const Log = mongoose.model('Log', logSchema);
module.exports = Log;
and router
const express = require('express')
const Log = require('../models/log')
const auth = require('../middleware/auth')
const router = new express.Router()
//Create log
router.post('/logs', async (req, res) => {
const log = new Log({
...req.body
})
try {
await log.save()
res.status(201).send(log)
} catch (e) {
res.status(400).send(e)
}
})
//Sort and search
router.get('/logs', auth, async (req, res) => {
const match = {}
const sort = {}
if (req.query.completed) {
match.completed = req.query.completed === 'true'
}
if (req.query.sortBy) {
const parts = req.query.sortBy.split(':')
sort[parts[0]] = parts[1] === 'desc' ? -1 : 1
}
try {
await req.user.populate({
path: 'logs',
match,
options: {
limit: parseInt(req.query.limit),
skip: parseInt(req.query.skip),
sort
}
}).execPopulate()
res.send(req.user.logs)
} catch (e) {
res.status(500).send()
}
})
module.exports = router;

Related

Getting Empty Object during Put request to the server

I created an API for following and followers user for Social Media Application, while request from postman getting empty object: {} But it seems to me to be correct.
Model:
const mongoose = require("mongoose");
const UserSchema = mongoose.Schema({
username: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
firstname: {
type: String,
required: true,
},
lastname: {
type: String,
required: true,
},
isAdmin: {
type: Boolean,
default: false,
},
profilePicture: String,
coverPicture: String,
about: String,
livesin: String,
workAt: String,
relationship: String,
followers: [],
following: []
},{
timestamps:true
}
);
const UserModel = mongoose.model("Users", UserSchema);
module.exports = UserModel;
UserControler:
const UserModel = require("../Models/Usermodel");
const bcrypt = require("bcryptjs");
const followUser = async (req, res) => {
const id = req.params.id.trim();
const { currentUserId } = req.body;
if (currentUserId === id) {
res.status(403).send("Action forbiden");
} else {
try {
const followUser = await UserModel.findById(id);
const followingUser = await UserModel.findById(currentUserId);
if (!followUser.followers.includes(currentUserId)) {
await followUser.updateOne({ $push: { followers: currentUserId } });
await followingUser.updateOne({ $push: { following: id } });
res.status(200).send({message:"User Followed"});
} else {
res.status(403).send("User alredy followed by you!");
}
} catch (error) {
res.status(500).send(error);
}
}
};
module.exports = { getUser, updateUser, userDelete, followUser };
UserRoute:
const express = require("express");
const {getUser,updateUser, userDelete, followUser} = require("../Controller/userControler");
const router = express.Router()
router.get("/:id",getUser)
router.put("/:id",updateUser)
router.delete("/:id", userDelete)
router.put("/:id/follow", followUser)
module.exports=router;
index.js:
app.use("/user",UserRoute)
Here is the complete details regarding the error, let me know what happens in the code, thank you.
i assume that you have all the other functions other than followUser in your controller.js
The thing is that you must first specify the field name on the basis of which you want to update the document.
Here is what you need to do;
const UserModel = require("../Models/Usermodel");
const bcrypt = require("bcryptjs");
const mongoose = require("mongoose");//updated line
const followUser = async (req, res) => {
const id = req.params.id.trim();
const { currentUserId } = req.body;
if (currentUserId === id) {
res.status(403).send("Action forbiden");
} else {
try {
const followUser = await UserModel.findById({_id: mongoose.Types.ObjectId(id)});
const followingUser = await UserModel.findById({_id: mongoose.Types.ObjectId(currentUserId)});
if (!followUser.followers.includes(currentUserId)) {
await followUser.updateOne({_id: mongoose.Types.ObjectId(*id of the user you want to update*)},{ $push: { followers: currentUserId } });
await followingUser.updateOne({_id: mongoose.Types.ObjectId(*id of the user you want to update*)}{ $push: { following: id } });
res.status(200).send({message:"User Followed"});
} else {
res.status(403).send("User alredy followed by you!");
}
} catch (error) {
res.status(500).send(error);
}
}
};
module.exports = { getUser, updateUser, userDelete, followUser };
And while hitting the api pls make sure that your route should be
localhost:port-number/user/12345789/follow
and also make sure that the API type in postman must be same as in the backend e.g; PUT
please try findByIdAndUpdate query insted of using updateOne

How should I have all the inner methods called as I try and test my login route?

Hello I am using mocha, chai, sinon and supertest.
I have a logging route I cannot stub/spy/mock (tried all 3) an inner class method authService.findByEmail(req.body.email) inside the loginRoute function
this is the function I want to test
login.js
const jwtGenerator = (payload) => jwt.sign(payload, process.env.TOKEN, { expiresIn: "1h" });
const loginRoute = async (req, res, next) => {
try {
req.body.password = String(req.body.password);
// db query trying to force a sinon.stub to resolve a fake value. But code wont pass here hence 500 error
const userQuery = await authService.findByEmail(req.body.email);
const compare = await bcrypt.compare(req.body.password, userQuery.password);
if (!compare) {
throw createError(401, 'Incorrect password.');
}
const user = {
id: userQuery.id, role: userQuery.is_admin ? "Administrator" : "User", email: userQuery.email, Authorized: true
}
const token = jwtGenerator(user);
return res
.cookie("access_token", token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
}).status(200).json({ message: "Logged in successfully ๐Ÿ˜Š ๐Ÿ‘Œ", user, token });
} catch (error) {
next(error);
}
}
router.post("/auth/login", loginRoute);
Here is my test file
login.spec.js
const chai = require("chai");
const sinonChai = require("sinon-chai");
chai.use(sinonChai);
const supertest = require("supertest");
const sinon = require("sinon");
const AuthService = require("../../services/authService"); // the class
describe("/login", () => {
const createAccessToken = (payload) => jwt.sign(payload, TOKEN, {expiresIn: "1h"});
const authService = new AuthService();
let userQuery, token, response, cookie;
const agent = supertest(app);
let loginDetail = {
email: "admin#test.com",
password: "123456"
};
let adminUser = {
id: 1,
email: "admin#test.com",
password: "123456",
is_admin: true
}
const user = {
id: 1,
email: "admin#test.com",
password: "123456",
is_admin: true
}
it("login controller function", async () => {
token = createAccessToken(user);
// I have commented out the set as they aren't doing anything atm
response = await agent.post("/auth/login").send(loginDetail)//.set("Accept", "application/json")
// .set("Cookie", `access-token=${token}`);
userQuery = sinon.stub(authService, "findByEmail").withArgs(loginDetail.email).resolves(adminUser);
// authService.findByEmail(loginDetail.email); // even if I call it the calledWith might work but response.status is still 500
// userQuery.resolves(adminUser);
expect(userQuery).to.have.been.calledWith(loginDetail.email); // is never called
// expect(userQuery).to.have.been.called // never called
expect(response.status).to.deep.equal(200); // returns 500
});
});
I must be doing something wrong somewhere I have tried everything I dont know how to have all the inner methods/functions inside the loginRoute to be called automatically as I call the loginRoute through supertest(app).post("/auth/login")

Am I using the Repository pattern correctly in NodeJS?

I use Sequelize to work with the database.
In my project, I encountered code duplication, and decided to study the repository design pattern, which will separate the work with the database and data output from the business logic. Having studied the information on the Internet, I decided to consolidate the material and check with more experienced programmers. I use the repository pattern correctly?
controller/user.controller.js
const userService = require('../services/user.service');
exports.userCreate = async (req, res, next) => {
try {
const user = await userService.userCreate(req.body);
return res.status(201).json(user);
} catch (e) {
return next(e);
}
}
services/user.service.js
const ApiError = require('../utils/error');
const userRepo = require('../repositories/user.repository');
exports.userCreate = async (data) => {
if (!data) throw ApiError.badRequest('Bad');
const { login } = data;
if (!login) throw ApiError.badRequest('Bad');
const password = 'hash';
const user = await userRepo.userCreate({login, password});
return user;
}
repositories/user.repository.js
const User = require('../models/User');
exports.userCreate = async (data) => {
const { login, passowrd } = data;
const user = await User.create({login, password});
return {
login: user.login,
}
}
models/User.js
const { sequelize, Sequelize } = require('../config/db');
const User = sequelize.define('users', {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
allowNull: false,
primaryKey: true,
},
login: {
type: Sequelize.STRING,
allowNull: false,
},
password: {
type: Sequelize.STRING,
allowNull: false,
}
});
module.exports = User;
Your User model is fine, but the structure of the service seems a little weird. if all three of the functions userCreates are in separate files I'd recommend you combine them and rename services to Controllers, as services are usually used on the client side of things.
services/user.service.js
const User = require('../models/User')
// const userRepo = require('../repositories/user.repository') Not sure what this does
const ApiError = require('../utils/error')
module.exports = {
async createUser (req, res) {
try {
if (!req.body) throw ApiError.badRequest('Bad')
const { login } = req.body
if (!login) throw ApiError.badRequest('Bad')
const password = 'hash'
const user = await User.create({ login, password })
res.stauts(201).json(user.login)
} catch (err) {
console.log(err)
res.staus(500).send({
error: err
})
// return next(err)
}
}
}

Jest test of function that contain an async call to User.findAll [sequelize postgresql] fails?

Iโ€™m trying to add a test to getAllUsers function but I donโ€™t know why the test is failing?
I think that the assertions are run before all the async call to User.findAllfinishes!!!
do you have any suggestions?
this is the file being tested:
const { Op } = require('sequelize')
const { User } = require('../models')
const catchAsync = require('../utils/catchAsync')
const AppError = require('../utils/appError')
exports.getAllUsers = catchAsync(async (req, res, next) => {
const users = await User.findAll({
attributes: ['id', 'username', 'email', 'role', 'avatar'],
where: {
id: { [Op.gt]: 0 }
}
})
if (!users.length) {
return next(new AppError('no data found', 204))
}
res.status(200).json({
status: 'success',
data: users
})
})
and this is the test code:
const userController = require('../controllers/userController')
describe('UserController', () => {
const users = [
{
username: 'Admin',
role: 'admin',
avatar: 'bb',
email: 'admin#gmail.com'
},
{
username: 'User',
role: 'user',
avatar: 'bb',
email: 'user#gmail.com'
}
]
test('Expect to respond with 200 code and users data', async () => {
const req = {}
const res = { status: jest.fn(() => res), json: jest.fn(() => res) }
const next = jest.fn()
await userController.getAllUsers(req, res, next)
expect(res.status).toHaveBeenCalledTimes(1)
expect(res.status).toHaveBeenCalledWith(200)
expect(res.json).toHaveBeenCalledTimes(1)
expect(res.json).toHaveBeenCalledWith({
status: 'success',
data: users
})
})
})
thanks for the help
Considering there's no pre-requisite (mocked database) that hasn't been shared in the question
When you run jest --coverage it will generate you a coverage report which would give you an overview of the code execution
above you can see that the if statement has not been executed, meaning that an error is thrown from User.findAll
To solve this: you can mock the User model and it's findAll's resolved value to overcome this
const userController = require('../controllers/userController');
const { User } = require('../models');
// generate auto-mock of the module
jest.mock('../models');
describe('UserController', () => {
const users = [
// .... same as in the question
];
beforeAll(() => {
// give the mock function a value
// for the promise to be resolved with
User.findAll.mockResolvedValue(users);
});
test('Expect to respond with 200 code and users data', async () => {
// .... same as in the question
});
});

Injecting dependecy in services in component based strucuture

I follow modular or component based strucutre. I found a sample repo.
https://github.com/sujeet-agrahari/node-express-clean-architecture
So, there is a main component.module.js files which is responsible for connecting all other pieces like controller, route, and services.
For controller, services are being injected using higher order functions. Now, controller are super easy to test, I can stub or mock services easily.
auth.module.js
const router = require('express').Router();
const {
makeExpressCallback,
makeValidatorCallback,
} = require('../../middlewares');
// validator
const AuthValidator = require('./auth.validator');
// service
const { doRegister, doLogin, doCheckUserExist } = require('./auth.service');
const { BadRequestError } = require('../../utils/api-errors');
// controller
const controller = require('./auth.controller');
const register = controller.register({ BadRequestError, doCheckUserExist, doRegister });
const login = controller.login({ doCheckUserExist, doLogin });
const AuthController = { register, login };
// routes
const routes = require('./auth.routes')({
router,
AuthController,
AuthValidator,
makeValidatorCallback,
makeExpressCallback,
});
module.exports = {
AuthController,
AuthService: {
doCheckUserExist,
doLogin,
doRegister,
},
AuthRoutes: routes,
};
auth.controller.js
const login = (doCheckUserExist, doLogin) => async (httpRequest) => {
const { username, password } = httpRequest.body;
const userData = await doCheckUserExist({ username });
const loginData = {
username,
role: userData.role_id,
passedPassword: password,
actualPassword: userData.password,
};
const loginResult = await doLogin(loginData);
return {
statusCode: 200,
body: {
success: true,
message: 'Successfully logged in!',
data: loginResult,
},
};
};
const register = ({ BadRequestError, doCheckUserExist, doRegister }) => async (httpRequest) => {
const { username, password } = httpRequest.body;
try {
await doCheckUserExist({ username });
} catch (error) {
// user doesn't exist
const registerResult = await doRegister({ username, password });
return {
statusCode: 200,
body: {
success: true,
message: 'Registered successfully!',
data: registerResult,
},
};
}
throw new BadRequestError('User already exist!');
};
module.exports = { register, login };
Things are fine with the controller, now the problem is with the services. I can't find any pattern to make them thin and clean.
auth.services.js
const {
JWT_ACCESS_TOKEN_SECRET,
ACCESS_TOKEN_EXPIRES_IN,
SIGN_OPTION,
} = require('config');
const bcrypt = require('bcryptjs');
const { User } = require('../../db');
const { generateJWT } = require('./jwt.service');
const { NotFoundError, BadRequestError } = require('../../utils/api-errors');
const doRegister = async ({ username, password }) => {
const user = await User.create({
username,
password,
role_id: 1, // assign role id here
});
// generate access token
const payload = {
username,
role: user.role_id,
};
const token = await generateJWT({
secretKey: JWT_ACCESS_TOKEN_SECRET,
payload,
signOption: {
...SIGN_OPTION,
expiresIn: ACCESS_TOKEN_EXPIRES_IN,
},
});
return {
access_token: token,
...payload,
};
};
const doLogin = async ({
username, userRole, passedPassword, actualPassword,
}) => {
const isValidPass = bcrypt.compareSync(passedPassword, actualPassword);
if (!isValidPass) throw new BadRequestError('Username or Password is invalid!');
// generate access token
const payload = {
username,
role: userRole,
};
const token = await generateJWT({
secretKey: JWT_ACCESS_TOKEN_SECRET,
payload,
signOption: {
...SIGN_OPTION,
expiresIn: ACCESS_TOKEN_EXPIRES_IN,
},
});
return {
access_token: token,
...payload,
};
};
const doCheckUserExist = async ({ username }) => {
const user = await User.findOne({
where: {
username,
},
});
if (!user) throw new NotFoundError('User not found!');
return user;
};
module.exports = { doRegister, doLogin, doCheckUserExist };
A lot is happening in the services, model imports, constants imports, and other utils.
Now services become really hard to test.
Is there any way or pattern I can separate some logic from services and make them lighter?
I can implement reository pattern for db methods, but I am not aware how I can implement using sequelize?
Should I use also higher order function to inject all the utils and constants in the service like I did for controller?

Categories

Resources