first commit

master
sajjad_talkhabi 10 months ago
commit 9ba07f99d5

11
.env

@ -0,0 +1,11 @@
# MongoDB Url
MONGO_URL=mongodb://localhost:27017/social_media
# PORT
PORT=5000
# Environment Name. production / development
NODE_ENV=development
# JWT Secret
JWT_SECRET=R4ND0M5TR1NG
# Gmail & Passsword
GMAIL=sajjad.t.dev@gmail.com
GMAIL_PASSWORD=tlvq semi gizc cyvn

@ -0,0 +1,11 @@
# MongoDB Url
MONGO_URL=
# PORT
PORT=
# Environment Name. production / development
NODE_ENV=
# JWT Secret
JWT_SECRET=
# Gmail & Passsword
GMAIL=
GMAIL_PASSWORD=

1
.gitignore vendored

@ -0,0 +1 @@
node_modules

@ -0,0 +1,3 @@
{
"nuxt.isNuxtApp": false
}

@ -0,0 +1,19 @@
'use strict';
const path = require('path');
module.exports.getConfig = () => {
const config = {
'MODE': 'Development',
'PORT': process.env.PORT || 5000,
'MONGO_URL': process.env.MONGO_URL,
'UPLOAD_PATH': path.resolve(`${__dirname}/../uploads`),
'JWT_SECRET': process.env.JWT_SECRET || 'R4ND0M5TR1NG'
};
// Modify for Production
if (process.env.NODE_ENV === 'production') {
config.MODE = 'Production';
}
return config;
};

@ -0,0 +1,26 @@
const mongoose = require('mongoose'),
config = require('./config').getConfig();
// Mongo Connection Class
class Connection {
constructor() {
const url = config.MONGO_URL || 'mongodb://localhost:27017/social_media';
this.connect(url).then(() => {
console.log('✔ Database Connected');
}).catch((err) => {
console.error('✘ MONGODB ERROR: ', err.message);
});
}
async connect(url) {
try {
await mongoose.connect(url);
} catch (e) {
throw e;
}
}
}
module.exports = new Connection();

@ -0,0 +1,40 @@
'use strict';
const express = require('express');
const path = require('path');
const { HttpError } = require('../system/helpers/HttpError');
const apiRoutes = require('../system/routes');
const bodyParser = require('body-parser');
module.exports.setRoutes = (app) => {
/**
* Application Root Route.
* Set the Welcome message or send a static html or use a view engine.
*/
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(express.json());
app.get('/', (req, res) => {
res.send('Welcome to the APP');
});
/**
* API Route.
* All the API will start with "/api/[MODULE_ROUTE]"
*/
app.use('/api', apiRoutes);
/**
* Serving Static files from uploads directory.
* Currently Media module is uploading files into this directory.
*/
app.use('/uploads', express.static(path.join(__dirname, '../uploads')));
/**
* If No route matches. Send user a 404 page
*/
app.use('/*', (req, res) => {
const error = new Error('Requested path does not exist.');
error.statusCode = 404;
res.status(error.statusCode).json(new HttpError(error));
});
};

@ -0,0 +1,19 @@
const express = require('express');
const helmet = require('helmet'),
server = express();
const { setRoutes } = require('./routes');
// For security
server.use(helmet());
const cors = require('cors'),
// Allow Origins according to your need.
corsOptions = {
'origin': '*'
};
server.use(cors(corsOptions));
// Setting up Routes
setRoutes(server);
module.exports = { server };

@ -0,0 +1,23 @@
require('dotenv').config();
// Initialize DB Connection
require('./config/database');
const config = require('./config/config').getConfig(),
PORT = config.PORT;
console.log('✔ Bootstrapping Application');
console.log(`✔ Mode: ${config.MODE}`);
console.log(`✔ Port: ${PORT}`);
const { server } = require('./config/server');
server.listen(PORT).on('error', (err) => {
console.log('✘ Application failed to start');
console.error('✘', err.message);
process.exit(0);
}).on('listening', () => {
console.log('✔ Application Started');
});
module.exports = { server };

3360
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,20 @@
{
"scripts": {
"start": "node ./bin/www",
"watch": "nodemon ./bin/www --watch ./ --ext '*' localhost 5000"
},
"dependencies": {
"auto-bind": "^5.0.1",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.2",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"helmet": "^7.0.0",
"jsonwebtoken": "^9.0.2",
"mongoose": "^8.0.0",
"nodemailer": "^6.9.7",
"nodemon": "^3.0.1",
"pluralize": "^8.0.0"
}
}

@ -0,0 +1,100 @@
const { AuthService } = require('./../services/AuthService');
const User = require('./../models/User');
// const autoBind = require('auto-bind');
const authService = new AuthService(User);
class AuthController {
constructor(service) {
this.service = service;
// autoBind(this);
this.register = this.register.bind(this)
this.verify = this.verify.bind(this)
this.login = this.login.bind(this)
this.forgotPassword = this.forgotPassword.bind(this)
this.resetPassword = this.resetPassword.bind(this)
}
async login(req, res, next) {
try {
const response = await this.service.login(req.body);
await res.status(response.statusCode).json(response);
} catch (e) {
next(e);
}
}
async register(req, res, next) {
try {
const response = await this.service.register(req.body);
await res.status(response.statusCode).json(response);
} catch (e) {
next(e);
}
}
async verify(req, res, next) {
try {
const token = this.extractToken(req);
const response = await this.service.verify(token);
console.log(response, 11);
await res.status(response.statusCode).json(response);
} catch (e) {
next(e);
}
}
async forgotPassword(req, res, next) {
try {
const response = await this.service.forgotPassword(req.body);
await res.status(response.statusCode).json(response);
} catch (e) {
next(e);
}
};
async resetPassword(req, res, next) {
try {
const token = this.extractToken(req);
const response = await this.service.resetPassword(token, req.body);
await res.status(response.statusCode).json(response);
} catch (e) {
next(e);
}
};
async logout(req, res, next) {
try {
const response = await this.service.logout(req.token);
await res.status(response.statusCode).json(response);
} catch (e) {
next(e);
}
}
async checkLogin(req, res, next) {
try {
const token = this.extractToken(req);
req.user = await this.service.checkLogin(token);
req.authorized = true;
req.token = token;
next();
} catch (e) {
next(e);
}
}
extractToken(req) {
if (req.headers.authorization && req.headers.authorization.split(' ')[0] === 'Bearer') {
return req.headers.authorization.split(' ')[1];
} else if (req.query && req.query.token) {
return req.query.token;
}
return null;
}
}
module.exports = new AuthController(authService);

@ -0,0 +1,19 @@
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
email: {
type: String,
required: true,
unique: true,
},
password: {
type: String,
required: true,
},
isVerified: {
type: Boolean,
default: false,
},
});
module.exports = mongoose.model('User', userSchema);

@ -0,0 +1,12 @@
const AUthController = require('../controllers/AuthController');
const express = require('express'),
router = express.Router();
router.post('/login', AUthController.login);
router.post('/verify', AUthController.verify);
router.get('/logout', AUthController.checkLogin, AUthController.logout);
router.post('/register', AUthController.register);
router.post('/forgot-password' ,AUthController.forgotPassword);
router.post('/reset-password' ,AUthController.resetPassword);
module.exports = router;

@ -0,0 +1,154 @@
const { Transporter } = require('../../system/services/GmailService');
const bcrypt = require('bcrypt'), SALT_WORK_FACTOR = 10;
const jwt = require('jsonwebtoken');
const { UserService } = require('./UserService');
// const autoBind = require('auto-bind');
const { HttpResponse } = require('../../system/helpers/HttpResponse');
const { HttpError } = require('../../system/helpers/HttpError');
class AuthService {
constructor(userModel) {
this.userModel = userModel;
this.userService = new UserService(userModel);
// autoBind(this);
};
/**
*
* @param email: String
* @param password: String
* @returns {Promise<any>}
*/
async login(data) {
try {
const { email, password } = data;
// Find the user by email
let user = await this.userModel.findOne({ email });
if (!user) return new HttpError({ message: 'Invalid email or password.', statusCode: 400 });
// Check if the password is correct
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) return new HttpError({ message: 'Invalid email or password.', statusCode: 400 });
// Check if the user is verified
if (!user.isVerified) return new HttpError({ message: 'Email not verified.', statusCode: 400 });
// Generate a JWT token
const token = jwt.sign({ userId: user._id }, 'token');
// serialize user to delete password
user = new HttpResponse().filterData(JSON.parse(JSON.stringify(user)))
return { statusCode: 200, message: 'Login successful', data: { user, token } }
} catch (error) {
return new HttpError({ message: 'Login failed. Please try again.', statusCode: 500 });
};
};
async register(data) {
try {
console.log(data);
const { email, password } = data;
// Check if email already exists
const existingUser = await this.userModel.findOne({ email });
if (existingUser) return new HttpError({ message: 'Email already exists.', statusCode: 400 });
// Hash the password,
const hashedPassword = await bcrypt.hash(password, SALT_WORK_FACTOR);
// Create a new user
const response = await this.userService.store({ email, password: hashedPassword });
const { data: user } = response;
// Generate a verification token
const token = jwt.sign({ userId: user._id }, 'token');
// Send verification email
await Transporter.sendMail({
from: process.env.GMAIL,
to: email,
subject: 'Email Verification',
text: `Please click the following link to verify your email: http://localhost:3000/verify?token=${token}`,
});
return { ...response, data: {} };
} catch (error) {
return new HttpError({ message: 'Registration failed. Please try again.', statusCode: 500 })
};
};
async verify(token) {
try {
// Verify the token
const decodedToken = jwt.verify(token, 'token');
const userId = decodedToken.userId;
// Find the user with the corresponding token
const user = await this.userModel.findById(userId);
if (!user) return new HttpError({ message: 'Invalid or expired token.', statusCode: 400 });
// Update the user's isVerified field
user.isVerified = true;
const response = await this.userService.update(userId, user);
return { ...response, message: 'Email verification successful.' };
} catch (error) {
return new HttpError({ message: 'Email verification failed. Please try again.', statusCode: 500 });
};
};
async forgotPassword(data) {
const { email } = data;
try {
const user = await this.userModel.findOne({ email });
if (!user) return new HttpError({ message: 'User not found', statusCode: 404 });
const token = jwt.sign({ userId: user._id }, 'token');
const mailOptions = {
from: process.env.GMAIL,
to: email,
subject: 'Password Reset Request',
html: `<p>Dear user,</p>
<p>You have requested to reset your password. Please click on the following link to reset your password:</p>
<a href="http://localhost:3000/reset-password?token=${token}">Reset Password</a>
<br>
<p>If you did not request this, please ignore this email.</p>`,
};
await Transporter.sendMail(mailOptions);
return { message: 'Password reset email sent successfully', statusCode: 200 };
} catch (error) {
console.log(error);
return new HttpError({ message: 'Internal server error', statusCode: 500 });
};
};
async resetPassword(token, data) {
const { new_password } = data;
try {
// Verify the token
const decodedToken = jwt.verify(token, 'token');
const userId = decodedToken.userId;
// Find the user with the corresponding token
const user = await this.userModel.findById(userId);
if (!user) return new HttpError({ message: 'Invalid or expired reset token', statusCode: 400 });
// Hash the password
const hashedPassword = await bcrypt.hash(new_password, SALT_WORK_FACTOR);
// Create a new user
const response = await this.userService.update(userId, { password: hashedPassword });
return { ...response, message: 'Password updated successfully' };
} catch (error) {
console.log(error);
return new HttpError({ message: 'Internal server error', statusCode: 500 });
};
};
async logout(token) {
try {
await this.model.deleteOne({ token });
return new HttpResponse({ 'logout': true });
} catch (error) {
throw error;
};
};
};
module.exports = { AuthService };

@ -0,0 +1,24 @@
const { Transporter } = require('../../system/services/GmailService');
const { Service } = require('../../system/services/Service');
// const autoBind = require('auto-bind');
class UserService extends Service {
constructor(model) {
console.log(model);
super(model);
this.model = model;
// autoBind(this);
}
async updatePassword(id, data) {
try {
await this.model.findByIdAndUpdate(id, data, { 'new': true });
return { 'passwordChanged': true };
} catch (errors) {
throw errors;
}
}
}
module.exports = { UserService };

@ -0,0 +1,73 @@
const autoBind = require('auto-bind');
class Controller {
/**
* Base Controller Layer
* @author Sunil Kumar Samanta
* @param service
*/
constructor(service) {
this.service = service;
autoBind(this);
}
async index(req, res, next) {
try {
const response = await this.service.index(req.query);
return res.status(response.statusCode).json(response);
} catch (e) {
next(e);
}
}
async show(req, res, next) {
const { id } = req.params;
try {
const response = await this.service.show(id);
return res.status(response.statusCode).json(response);
} catch (e) {
next(e);
}
}
async store(req, res, next) {
try {
const response = await this.service.store(req.body);
return res.status(response.statusCode).json(response);
} catch (e) {
next(e);
}
}
async update(req, res, next) {
const { id } = req.params;
try {
const response = await this.service.update(id, req.body);
return res.status(response.statusCode).json(response);
} catch (e) {
next(e);
}
}
async delete(req, res, next) {
const { id } = req.params;
try {
const response = await this.service.delete(id);
return res.status(response.statusCode).json(response);
} catch (e) {
next(e);
}
}
}
module.exports = { Controller };

@ -0,0 +1,48 @@
'use strict';
class HttpError {
error = true;
responseTimestamp = new Date();
/**
* HTTP Error Class
* @author Sunil Kumar Samanta
* @param error
*/
constructor(error) {
if (typeof (error) === 'string') {
this.statusCode = 500;
this.message = error;
this.name = 'InternalServerError';
} else {
if (error.name === 'ValidationError') {
error.statusCode = 422;
}
let errorName = 'InternalServerError';
switch (error.statusCode) {
case 422:
errorName = 'ValidationError';
break;
case 401:
errorName = 'UnauthorizedError';
break;
case 403:
errorName = 'ForbiddenError';
break;
case 404:
errorName = 'NotFoundError';
break;
default:
errorName = 'InternalServerError';
}
this.statusCode = error.statusCode ? error.statusCode : 500;
this.message = error.message || 'Something wrong!';
this.errors = error.errors;
this.name = errorName;
}
}
}
module.exports = { HttpError };

@ -0,0 +1,53 @@
const defaultExcludedItemsFromResponse = ['__v', 'password'];
class HttpResponse {
/**
* @author Sunil Kumar Samanta
* @param data : Object | Array | String
* @param options : {totalCount: Number, statusCode: Number, deleted: Boolean}
*/
error = false;
responseTimestamp = new Date();
constructor(data, options = { 'totalCount': 0, 'statusCode': 200, 'deleted': null }) {
this.statusCode = options.statusCode || 200;
let filteredData = data;
if (typeof (filteredData) === 'object') {
filteredData = this.filterData(JSON.parse(JSON.stringify(filteredData)));
}
if (options.deleted) {
this.deleted = options.deleted;
}
if (Array.isArray(filteredData)) {
this.data = [...filteredData];
this.totalCount = options.totalCount || undefined;
} else if (typeof (filteredData) === 'object') {
this.data = { ...filteredData };
} else {
this.data = data;
}
}
filterData(data) {
if (Array.isArray(data)) {
data.map((x, index) => {
Object.keys(x).forEach((key) => {
if (defaultExcludedItemsFromResponse.includes(key)) {
delete data[index][key];
}
});
});
} else if (typeof (data) === 'object') {
Object.keys(data).forEach((key) => {
if (defaultExcludedItemsFromResponse.includes(key)) {
delete data[key];
}
});
}
return data;
}
}
module.exports = { HttpResponse };

@ -0,0 +1,8 @@
module.exports.slugify = (text) => {
return text.toString().toLowerCase()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/[^\w\-\.]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-') // Replace multiple - with single -
.replace(/^-+/, '') // Trim - from start of text
.replace(/-+$/, ''); // Trim - from end of text
};

@ -0,0 +1,55 @@
const express = require('express');
const router = express.Router();
const pluralize = require('pluralize');
const path = require('path');
pluralize.addUncountableRule('media');
pluralize.addUncountableRule('auth');
const fs = require('fs');
const { HttpError } = require('../helpers/HttpError');
const packageJson = require('../../package.json'),
routesPath = path.resolve(`${__dirname}/../../src/routes`),
PATHS = fs.readdirSync(routesPath),
moduleMapper = [];
console.log('✔ Mapping routes');
PATHS.forEach((module) => {
if (module !== 'index.js') {
const name = module.split('.')[0];
// eslint-disable-next-line global-require
router.use(`/${pluralize.plural(name)}`, require(path.resolve(routesPath, module)));
moduleMapper.push({
'Module': name,
'Route': `/${pluralize.plural(name)}`
});
}
});
console.table(moduleMapper);
router.get('/', (req, res) => {
res.json({ 'status': true, 'message': `Welcome to ${packageJson.name} V ${packageJson.version}` });
});
router.use('*', (req, res, next) => {
// 404 handler
const error = new Error('Resource not found');
error.statusCode = 404;
next(error);
});
router.use((err, req, res, next) => {
if (process.env.NODE_ENV !== 'production') {
console.error(req.method, req.url, err.statusCode, err.message);
}
const error = new HttpError(err);
res.status(error.statusCode);
res.json(error);
next();
});
module.exports = router;

@ -0,0 +1,9 @@
const nodemailer = require('nodemailer');
const Transporter = nodemailer.createTransport({
service: 'Gmail',
auth: {
user: process.env.GMAIL,
pass: process.env.GMAIL_PASSWORD,
},
});
module.exports = { Transporter };

@ -0,0 +1,112 @@
const mongoose = require('mongoose');
// const autoBind = require('auto-bind');
const { HttpResponse } = require('../helpers/HttpResponse');
class Service {
/**
* Base Service Layer
* @author Sunil Kumar Samanta
* @param model
*/
constructor(model) {
console.log(model, 32);
this.model = model;
// autoBind(this);
}
async index(query) {
let { skip, limit, sortBy } = query;
skip = skip ? Number(skip) : 0;
limit = limit ? Number(limit) : 10;
sortBy = sortBy ? sortBy : { 'createdAt': -1 };
delete query.skip;
delete query.limit;
delete query.sortBy;
if (query._id) {
try {
query._id = new mongoose.mongo.ObjectId(query._id);
} catch (error) {
throw new Error('Not able to generate mongoose id with content');
}
}
try {
const items = await this.model
.find(query)
.sort(sortBy)
.skip(skip)
.limit(limit),
total = await this.model.countDocuments(query);
return new HttpResponse(items, { 'totalCount': total });
} catch (errors) {
throw errors;
}
}
async show(id) {
try {
const item = await this.model.findById(id);
if (!item) {
const error = new Error('Item not found');
error.statusCode = 404;
throw error;
}
return new HttpResponse(item);
} catch (errors) {
throw errors;
}
}
async store(data) {
try {
const item = await this.model.create(data);
if (item) {
return new HttpResponse(item);
}
throw new Error('Something wrong happened');
} catch (error) {
throw error;
}
}
async update(id, data) {
try {
const item = await this.model.findByIdAndUpdate(id, data, { 'new': true });
return new HttpResponse(item);
} catch (errors) {
throw errors;
}
}
async delete(id) {
try {
const item = await this.model.findByIdAndDelete(id);
if (!item) {
const error = new Error('Item not found');
error.statusCode = 404;
throw error;
} else {
return new HttpResponse(item, { 'deleted': true });
}
} catch (errors) {
throw errors;
}
}
}
module.exports = { Service };
Loading…
Cancel
Save