broll-express 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/.babelrc +6 -0
- package/LICENSE +21 -0
- package/package.json +32 -0
- package/src/app.js +32 -0
- package/src/config/mongoConfig.js +9 -0
- package/src/config/swaggerConfig.js +27 -0
- package/src/controllers/auth.controller.js +22 -0
- package/src/controllers/signup.controller.js +31 -0
- package/src/library/auth.lib.js +4 -0
- package/src/library/error.lib.js +11 -0
- package/src/middlewares/errorHandler.middleware.js +19 -0
- package/src/middlewares/payloadValidation.middleware.js +19 -0
- package/src/models/auth.models.js +63 -0
- package/src/models/common.schema.js +10 -0
- package/src/models/user.models.js +30 -0
- package/src/routes/auth.routes.js +30 -0
- package/src/routes/business.routes.js +5 -0
- package/src/routes/public.routes.js +2 -0
- package/src/routes/user.routes.js +0 -0
- package/src/server.js +13 -0
- package/src/validations/validations.schema.js +53 -0
- package/start.js +11 -0
package/.babelrc
ADDED
package/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2024 Ananda krishnan g r
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
package/package.json
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
{
|
2
|
+
"name": "broll-express",
|
3
|
+
"version": "1.0.0",
|
4
|
+
"main": "index.js",
|
5
|
+
"type": "module",
|
6
|
+
"scripts": {
|
7
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
8
|
+
"dev": " env-cmd -f .env.development node --no-warnings --watch --loader @babel/register ./start.js"
|
9
|
+
},
|
10
|
+
"author": "anandakrishnangr",
|
11
|
+
"license": "ISC",
|
12
|
+
"description": "",
|
13
|
+
"dependencies": {
|
14
|
+
"babel-node": "^0.0.1-security",
|
15
|
+
"bcryptjs": "^2.4.3",
|
16
|
+
"body-parser": "^1.20.3",
|
17
|
+
"cors": "^2.8.5",
|
18
|
+
"env-cmd": "^10.1.0",
|
19
|
+
"express": "^4.21.1",
|
20
|
+
"joi": "^17.13.3",
|
21
|
+
"jsonwebtoken": "^9.0.2",
|
22
|
+
"mongoose": "^8.7.1",
|
23
|
+
"swagger-jsdoc": "^6.2.8",
|
24
|
+
"swagger-ui-express": "^5.0.1"
|
25
|
+
},
|
26
|
+
"devDependencies": {
|
27
|
+
"@babel/core": "^7.25.8",
|
28
|
+
"@babel/node": "^7.25.7",
|
29
|
+
"@babel/preset-env": "^7.25.8",
|
30
|
+
"@babel/register": "^7.25.7"
|
31
|
+
}
|
32
|
+
}
|
package/src/app.js
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
import express from 'express';
|
2
|
+
import { connectMongoose } from './config/mongoConfig.js'
|
3
|
+
import bodyParser from 'body-parser';
|
4
|
+
import { createUser } from './controllers/signup.controller.js';
|
5
|
+
import authRoute from './routes/auth.routes.js'
|
6
|
+
import { validateRequest } from './middlewares/payloadValidation.middleware.js';
|
7
|
+
import cors from 'cors'
|
8
|
+
import { swaggerSpecs } from "./config/swaggerConfig.js";
|
9
|
+
import swaggerUi from "swagger-ui-express"
|
10
|
+
|
11
|
+
const app = express();
|
12
|
+
connectMongoose()
|
13
|
+
// Middleware to parse JSON requests
|
14
|
+
app.use(cors());
|
15
|
+
app.use(express.json());
|
16
|
+
app.use(bodyParser.json({
|
17
|
+
limit: '5mb'
|
18
|
+
}));
|
19
|
+
app.use(bodyParser.urlencoded({ extended: false }))
|
20
|
+
app.use(bodyParser.json())
|
21
|
+
app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpecs));
|
22
|
+
|
23
|
+
app.use(validateRequest)
|
24
|
+
app.get('/', (req, res) => {
|
25
|
+
res.send('Welcome to the Demo App!');
|
26
|
+
});
|
27
|
+
|
28
|
+
app.post('/test', createUser)
|
29
|
+
// app.post('/business',)
|
30
|
+
app.use("/auth", authRoute)
|
31
|
+
|
32
|
+
export default app // Export the app for use in server.js
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import swaggerJsdoc from 'swagger-jsdoc'
|
2
|
+
import { fileURLToPath } from 'url';
|
3
|
+
import packageDetails from '../../package.json' assert {type: 'json'}
|
4
|
+
import path from 'path';
|
5
|
+
const PORT = process.env.PORT
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
7
|
+
const __dirname = path.dirname(__filename);
|
8
|
+
const options = {
|
9
|
+
definition: {
|
10
|
+
openapi: "3.0.0",
|
11
|
+
info: {
|
12
|
+
title: "Ecom API with Swagger",
|
13
|
+
version: packageDetails.version,
|
14
|
+
description: "A simple CRUD API application made with Express and documented with Swagger",
|
15
|
+
},
|
16
|
+
servers: [
|
17
|
+
{
|
18
|
+
url: `http://localhost:${PORT}`, // Replace with your server URL
|
19
|
+
},
|
20
|
+
],
|
21
|
+
},
|
22
|
+
apis: [path.join(__dirname, "../routes/*.js")],
|
23
|
+
};
|
24
|
+
|
25
|
+
export const swaggerSpecs = swaggerJsdoc(options);
|
26
|
+
|
27
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
import Login from "../models/auth.models.js"
|
2
|
+
|
3
|
+
export const register = async (req, res, next) => {
|
4
|
+
try {
|
5
|
+
let { password, email } = req.body
|
6
|
+
await Login.create({ password, email })
|
7
|
+
res.send(200)
|
8
|
+
} catch (error) {
|
9
|
+
next(error)
|
10
|
+
}
|
11
|
+
}
|
12
|
+
|
13
|
+
export const login = async (req, res, next) => {
|
14
|
+
try {
|
15
|
+
let { password, email } = req.body
|
16
|
+
await Login.isUserAuthenticated(email, password)
|
17
|
+
res.send(200)
|
18
|
+
} catch (error) {
|
19
|
+
console.log(error)
|
20
|
+
next(error)
|
21
|
+
}
|
22
|
+
}
|
@@ -0,0 +1,31 @@
|
|
1
|
+
import mongoose from "mongoose"
|
2
|
+
import user from "../models/user.models.js";
|
3
|
+
|
4
|
+
|
5
|
+
export const createUser = async(req, res) => {
|
6
|
+
const session = await mongoose.startSession();
|
7
|
+
|
8
|
+
try {
|
9
|
+
session.startTransaction();
|
10
|
+
|
11
|
+
// Create a new address
|
12
|
+
const newUser = await user.create([{
|
13
|
+
firstName:"ananda",
|
14
|
+
lastName:"krishnan",
|
15
|
+
gender:"male"
|
16
|
+
}], { session });
|
17
|
+
|
18
|
+
// Commit the transaction
|
19
|
+
throw Error
|
20
|
+
await session.commitTransaction();
|
21
|
+
console.log('Transaction committed:', newUser);
|
22
|
+
} catch (error) {
|
23
|
+
// If an error occurs, abort the transaction
|
24
|
+
await session.abortTransaction();
|
25
|
+
console.error('Transaction aborted due to error:', error);
|
26
|
+
} finally {
|
27
|
+
session.endSession();
|
28
|
+
}
|
29
|
+
|
30
|
+
|
31
|
+
}
|
@@ -0,0 +1,11 @@
|
|
1
|
+
class AppError extends Error {
|
2
|
+
constructor({ message, data, statusCode }) {
|
3
|
+
super(data);
|
4
|
+
this.data = data
|
5
|
+
this.message = message
|
6
|
+
this.statusCode = statusCode;
|
7
|
+
this.status = `${statusCode}`.startsWith('4') ? 'fail' : 'error';
|
8
|
+
this.isOperational = true; // Flag to indicate it's an expected error
|
9
|
+
}
|
10
|
+
}
|
11
|
+
export default AppError
|
@@ -0,0 +1,19 @@
|
|
1
|
+
function globalErrorHandler(err, req, res, next) {
|
2
|
+
let statusCode = err.statusCode || 500;
|
3
|
+
let message = err.message || 'Internal Server Error';
|
4
|
+
let data = err.data || {};
|
5
|
+
console.log(err.name)
|
6
|
+
if (err.name === 'ValidationError') {
|
7
|
+
message = Object.values(err.errors).map(err => err.message);
|
8
|
+
console.error('Validation Errors:', message);
|
9
|
+
} else {
|
10
|
+
|
11
|
+
}
|
12
|
+
res.status(statusCode).json({
|
13
|
+
status: err.status || 'error',
|
14
|
+
message,
|
15
|
+
data
|
16
|
+
});
|
17
|
+
}
|
18
|
+
|
19
|
+
export default globalErrorHandler
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import { schemaLookup } from "../validations/validations.schema.js";
|
2
|
+
|
3
|
+
export const validateRequest = (req, res, next) => {
|
4
|
+
const { method, path } = req;
|
5
|
+
console.log(path, method)
|
6
|
+
|
7
|
+
const schema = schemaLookup[`${method}:${path}`] || Object.keys(schemaLookup).find(key => {
|
8
|
+
const regex = new RegExp(`^${key.split(':')[1].replace(':id', '\\d+')}$`);
|
9
|
+
return regex.test(path) && key.startsWith(method);
|
10
|
+
});
|
11
|
+
if (schema) {
|
12
|
+
const { error } = schema.validate(req.body);
|
13
|
+
if (error) {
|
14
|
+
return res.status(400).json({ message: error.details[0].message });
|
15
|
+
}
|
16
|
+
}
|
17
|
+
next(); // Proceed to the next middleware or route handler
|
18
|
+
};
|
19
|
+
|
@@ -0,0 +1,63 @@
|
|
1
|
+
import mongoose from "mongoose";
|
2
|
+
import { VerificationDetailsSchema } from "./common.schema.js";
|
3
|
+
import { comparePassword, hashPassword } from "../library/auth.lib.js";
|
4
|
+
import AppError from "../library/error.lib.js";
|
5
|
+
|
6
|
+
const LoginSchema = mongoose.Schema({
|
7
|
+
email: {
|
8
|
+
type: String,
|
9
|
+
unique: true,
|
10
|
+
trim: true,
|
11
|
+
required: [true, 'Email is required'],
|
12
|
+
match: [/.+@.+\..+/, 'Please enter a valid email address'],
|
13
|
+
lowercase: true,
|
14
|
+
},
|
15
|
+
phone: {
|
16
|
+
type: Number,
|
17
|
+
trim: true,
|
18
|
+
required: false,
|
19
|
+
min: 10
|
20
|
+
},
|
21
|
+
password: {
|
22
|
+
type: String,
|
23
|
+
trim: true,
|
24
|
+
required: [true, 'Password is required'],
|
25
|
+
minlength: [6, 'Password must be at least 6 characters long'],
|
26
|
+
},
|
27
|
+
email_verification_Details: VerificationDetailsSchema,
|
28
|
+
phone_verification_Details: VerificationDetailsSchema
|
29
|
+
}, {
|
30
|
+
timestamps: true
|
31
|
+
});
|
32
|
+
|
33
|
+
LoginSchema.pre('save', async function (next) {
|
34
|
+
this.password = await hashPassword(this.password)
|
35
|
+
next();
|
36
|
+
});
|
37
|
+
|
38
|
+
LoginSchema.post(['find', 'findOne'], async function (docs) {
|
39
|
+
const _includePassword = this.options.includePassword
|
40
|
+
if (_includePassword) {
|
41
|
+
return docs
|
42
|
+
}
|
43
|
+
if (Array.isArray(docs)) {
|
44
|
+
docs.forEach(doc => {
|
45
|
+
doc.password = undefined; // Remove password from each document
|
46
|
+
});
|
47
|
+
} else if (docs) {
|
48
|
+
docs.password = undefined; // Remove password from the single document
|
49
|
+
}
|
50
|
+
});
|
51
|
+
|
52
|
+
LoginSchema.statics.isUserAuthenticated = async function (email, password) {
|
53
|
+
let user = await this.findOne({ email }).setOptions({ includePassword: true });
|
54
|
+
if (!user) {
|
55
|
+
throw new AppError({ message: 'User Not Found !', statusCode: 401 })
|
56
|
+
}
|
57
|
+
const isMatch = await comparePassword(password, user.password)
|
58
|
+
if (!isMatch) throw new AppError({ message: 'Incorrect password', statusCode: 400 })
|
59
|
+
return isMatch
|
60
|
+
}
|
61
|
+
|
62
|
+
const Login = mongoose.model("Loginsf", LoginSchema);
|
63
|
+
export default Login;
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import mongoose from "mongoose";
|
2
|
+
|
3
|
+
const userSchema = mongoose.Schema({
|
4
|
+
firstName:{
|
5
|
+
type:String,
|
6
|
+
required:true,
|
7
|
+
trim:true
|
8
|
+
},
|
9
|
+
lastName:{
|
10
|
+
type:String,
|
11
|
+
trim:true
|
12
|
+
},
|
13
|
+
LastUpdatedAt:{
|
14
|
+
type: Date,
|
15
|
+
default: Date.now,
|
16
|
+
},
|
17
|
+
createdAt: {
|
18
|
+
type: Date,
|
19
|
+
default: Date.now,
|
20
|
+
},
|
21
|
+
gender: {
|
22
|
+
type: String,
|
23
|
+
enum: ['male', 'female'], // Enum values for gender
|
24
|
+
required: true,
|
25
|
+
},
|
26
|
+
})
|
27
|
+
|
28
|
+
|
29
|
+
const user = mongoose.model('customerUser', userSchema);
|
30
|
+
export default user;
|
@@ -0,0 +1,30 @@
|
|
1
|
+
import express from 'express';
|
2
|
+
import { register, login } from '../controllers/auth.controller.js';
|
3
|
+
|
4
|
+
const router = express.Router();
|
5
|
+
|
6
|
+
/**
|
7
|
+
* @swagger
|
8
|
+
* /auth/register:
|
9
|
+
* post:
|
10
|
+
* summary: Register a new user
|
11
|
+
* responses:
|
12
|
+
* 200:
|
13
|
+
* description: User registered successfully.
|
14
|
+
* 400:
|
15
|
+
* description: Bad request
|
16
|
+
*/
|
17
|
+
router.post('/register', register);
|
18
|
+
|
19
|
+
/**
|
20
|
+
* @swagger
|
21
|
+
* /auth/login:
|
22
|
+
* post:
|
23
|
+
* summary: User login
|
24
|
+
* responses:
|
25
|
+
* 200:
|
26
|
+
* description: User logged in successfully.
|
27
|
+
*/
|
28
|
+
router.post('/login', login);
|
29
|
+
|
30
|
+
export default router;
|
File without changes
|
package/src/server.js
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
import app from "./app.js"
|
2
|
+
|
3
|
+
import globalErrorHandler from "./middlewares/errorHandler.middleware.js";
|
4
|
+
const PORT = process.env.PORT || 3000;
|
5
|
+
const ENV = process.env.NODE_ENV
|
6
|
+
|
7
|
+
|
8
|
+
app.use(globalErrorHandler)
|
9
|
+
|
10
|
+
|
11
|
+
app.listen(PORT, () => {
|
12
|
+
console.log(`Server is running on http://localhost:${PORT} at ${ENV}`);
|
13
|
+
});
|
@@ -0,0 +1,53 @@
|
|
1
|
+
import Joi from 'joi';
|
2
|
+
|
3
|
+
// Define your schemas (same as before)
|
4
|
+
const userSchema = Joi.object({
|
5
|
+
email: Joi.string()
|
6
|
+
.email()
|
7
|
+
.optional()
|
8
|
+
.messages({
|
9
|
+
'string.email': 'Please enter a valid email address',
|
10
|
+
}),
|
11
|
+
phone: Joi.number()
|
12
|
+
.optional()
|
13
|
+
.min(1000000000)
|
14
|
+
.max(9999999999)
|
15
|
+
.messages({
|
16
|
+
'number.min': 'Phone number must be at least 10 digits',
|
17
|
+
'number.max': 'Phone number must be at most 10 digits',
|
18
|
+
}),
|
19
|
+
password: Joi.string()
|
20
|
+
.min(6)
|
21
|
+
.optional()
|
22
|
+
.pattern(/^[a-zA-Z0-9]{3,30}$/)
|
23
|
+
.messages({
|
24
|
+
'string.min': 'Password must be at least 6 characters long',
|
25
|
+
'string.pattern.base': 'Password can only contain alphanumeric characters',
|
26
|
+
}),
|
27
|
+
}).xor('email', 'phone')
|
28
|
+
.messages({
|
29
|
+
'object.xor': 'Either email or phone number must be provided.',
|
30
|
+
});
|
31
|
+
|
32
|
+
// Schema configuration mapping
|
33
|
+
const schemaConfig = [
|
34
|
+
{
|
35
|
+
methods: ['POST', 'PUT'],
|
36
|
+
paths: ['/auth/login', '/users/:id'],
|
37
|
+
schema: userSchema,
|
38
|
+
},
|
39
|
+
// Add more route configurations as needed
|
40
|
+
];
|
41
|
+
|
42
|
+
// Preprocess the schema configuration into a lookup map
|
43
|
+
const schemaLookup = {};
|
44
|
+
|
45
|
+
schemaConfig.forEach(route => {
|
46
|
+
route.methods.forEach(method => {
|
47
|
+
route.paths.forEach(path => {
|
48
|
+
schemaLookup[`${method}:${path}`] = route.schema;
|
49
|
+
});
|
50
|
+
});
|
51
|
+
});
|
52
|
+
|
53
|
+
export { schemaLookup, userSchema };
|
package/start.js
ADDED