This commit is contained in:
Kar l5
2024-08-07 21:43:47 +05:30
commit 2677abe35f
97 changed files with 7134 additions and 0 deletions

88
src/app.js Normal file
View File

@@ -0,0 +1,88 @@
const express = require('express');
const helmet = require('helmet');
const xss = require('xss-clean');
const mongoSanitize = require('express-mongo-sanitize');
const compression = require('compression');
const cors = require('cors');
const passport = require('passport');
const httpStatus = require('http-status');
const config = require('./config/config');
const morgan = require('./config/morgan');
const { jwtStrategy } = require('./config/passport');
const { authLimiter } = require('./middlewares/rateLimiter');
const routes = require('./routes/v1');
const { errorConverter, errorHandler } = require('./middlewares/error');
const ApiError = require('./utils/ApiError');
const bodyParser = require('body-parser');
const app = express();
if (config.env !== 'test') {
app.use(morgan.successHandler);
app.use(morgan.errorHandler);
}
// Middleware to parse JSON bodies
// app.use(bodyParser.json());
app.use(bodyParser.json({ limit: '10mb' }));
// set security HTTP headers
app.use(helmet());
// parse json request body
app.use(express.json());
// parse urlencoded request body
app.use(express.urlencoded({ extended: true }));
// sanitize request data
app.use(xss());
app.use(mongoSanitize());
// gzip compression
app.use(compression());
// enable cors
app.use(cors());
app.options('*', cors());
//# Need to implement
// const allowedOrigins = process.env.CORS_ALLOWED_ORIGINS.split(',');
// const corsOptions = {
// origin: function (origin, callback) {
// // Allow requests with no origin (like mobile apps, curl requests)
// if (!origin) return callback(null, true);
// if (allowedOrigins.indexOf(origin) !== -1) {
// callback(null, true);
// } else {
// callback(new Error('Not allowed by CORS'));
// }
// }
// };
// app.use(cors(corsOptions));
// app.use(cors());
// jwt authentication
app.use(passport.initialize());
passport.use('jwt', jwtStrategy);
// limit repeated failed requests to auth endpoints
if (config.env === 'production') {
app.use('/v1/auth', authLimiter);
}
// v1 api routes
app.use('/', routes);
// send back a 404 error for any unknown api request
app.use((req, res, next) => {
next(new ApiError(httpStatus.NOT_FOUND, 'Not found'));
});
// convert error to ApiError, if needed
app.use(errorConverter);
// handle error
app.use(errorHandler);
module.exports = app;

64
src/config/config.js Normal file
View File

@@ -0,0 +1,64 @@
const dotenv = require('dotenv');
const path = require('path');
const Joi = require('joi');
dotenv.config({ path: path.join(__dirname, '../../.env') });
const envVarsSchema = Joi.object()
.keys({
NODE_ENV: Joi.string().valid('production', 'development', 'test').required(),
PORT: Joi.number().default(3000),
MONGODB_URL: Joi.string().required().description('Mongo DB url'),
JWT_SECRET: Joi.string().required().description('JWT secret key'),
JWT_ACCESS_EXPIRATION_MINUTES: Joi.number().default(30).description('minutes after which access tokens expire'),
JWT_REFRESH_EXPIRATION_DAYS: Joi.number().default(30).description('days after which refresh tokens expire'),
JWT_RESET_PASSWORD_EXPIRATION_MINUTES: Joi.number()
.default(10)
.description('minutes after which reset password token expires'),
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: Joi.number()
.default(10)
.description('minutes after which verify email token expires'),
SMTP_HOST: Joi.string().description('server that will send the emails'),
SMTP_PORT: Joi.number().description('port to connect to the email server'),
SMTP_USERNAME: Joi.string().description('username for email server'),
SMTP_PASSWORD: Joi.string().description('password for email server'),
EMAIL_FROM: Joi.string().description('the from field in the emails sent by the app'),
})
.unknown();
const { value: envVars, error } = envVarsSchema.prefs({ errors: { label: 'key' } }).validate(process.env);
if (error) {
throw new Error(`Config validation error: ${error.message}`);
}
module.exports = {
env: envVars.NODE_ENV,
port: envVars.PORT,
mongoose: {
url: envVars.MONGODB_URL + (envVars.NODE_ENV === 'test' ? '-test' : ''),
options: {
useCreateIndex: true,
useNewUrlParser: true,
useUnifiedTopology: true,
},
},
jwt: {
secret: envVars.JWT_SECRET,
accessExpirationMinutes: envVars.JWT_ACCESS_EXPIRATION_MINUTES,
refreshExpirationDays: envVars.JWT_REFRESH_EXPIRATION_DAYS,
resetPasswordExpirationMinutes: envVars.JWT_RESET_PASSWORD_EXPIRATION_MINUTES,
verifyEmailExpirationMinutes: envVars.JWT_VERIFY_EMAIL_EXPIRATION_MINUTES,
},
email: {
smtp: {
host: envVars.SMTP_HOST,
port: envVars.SMTP_PORT,
auth: {
user: envVars.SMTP_USERNAME,
pass: envVars.SMTP_PASSWORD,
},
},
from: envVars.EMAIL_FROM,
},
};

26
src/config/logger.js Normal file
View File

@@ -0,0 +1,26 @@
const winston = require('winston');
const config = require('./config');
const enumerateErrorFormat = winston.format((info) => {
if (info instanceof Error) {
Object.assign(info, { message: info.stack });
}
return info;
});
const logger = winston.createLogger({
level: config.env === 'development' ? 'debug' : 'info',
format: winston.format.combine(
enumerateErrorFormat(),
config.env === 'development' ? winston.format.colorize() : winston.format.uncolorize(),
winston.format.splat(),
winston.format.printf(({ level, message }) => `${level}: ${message}`)
),
transports: [
new winston.transports.Console({
stderrLevels: ['error'],
}),
],
});
module.exports = logger;

24
src/config/morgan.js Normal file
View File

@@ -0,0 +1,24 @@
const morgan = require('morgan');
const config = require('./config');
const logger = require('./logger');
morgan.token('message', (req, res) => res.locals.errorMessage || '');
const getIpFormat = () => (config.env === 'production' ? ':remote-addr - ' : '');
const successResponseFormat = `${getIpFormat()}:method :url :status - :response-time ms`;
const errorResponseFormat = `${getIpFormat()}:method :url :status - :response-time ms - message: :message`;
const successHandler = morgan(successResponseFormat, {
skip: (req, res) => res.statusCode >= 400,
stream: { write: (message) => logger.info(message.trim()) },
});
const errorHandler = morgan(errorResponseFormat, {
skip: (req, res) => res.statusCode < 400,
stream: { write: (message) => logger.error(message.trim()) },
});
module.exports = {
successHandler,
errorHandler,
};

30
src/config/passport.js Normal file
View File

@@ -0,0 +1,30 @@
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
const config = require('./config');
const { tokenTypes } = require('./tokens');
const { User } = require('../models');
const jwtOptions = {
secretOrKey: config.jwt.secret,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
};
const jwtVerify = async (payload, done) => {
try {
if (payload.type !== tokenTypes.ACCESS) {
throw new Error('Invalid token type');
}
const user = await User.findById(payload.sub);
if (!user) {
return done(null, false);
}
done(null, user);
} catch (error) {
done(error, false);
}
};
const jwtStrategy = new JwtStrategy(jwtOptions, jwtVerify);
module.exports = {
jwtStrategy,
};

12
src/config/roles.js Normal file
View File

@@ -0,0 +1,12 @@
const allRoles = {
user: [],
admin: ['getUsers', 'manageUsers'],
};
const roles = Object.keys(allRoles);
const roleRights = new Map(Object.entries(allRoles));
module.exports = {
roles,
roleRights,
};

10
src/config/tokens.js Normal file
View File

@@ -0,0 +1,10 @@
const tokenTypes = {
ACCESS: 'access',
REFRESH: 'refresh',
RESET_PASSWORD: 'resetPassword',
VERIFY_EMAIL: 'verifyEmail',
};
module.exports = {
tokenTypes,
};

View File

@@ -0,0 +1,59 @@
const httpStatus = require('http-status');
const catchAsync = require('../utils/catchAsync');
const { authService, userService, tokenService, emailService } = require('../services');
const register = catchAsync(async (req, res) => {
const user = await userService.createUser(req.body);
const tokens = await tokenService.generateAuthTokens(user);
res.status(httpStatus.CREATED).send({ user, tokens });
});
const login = catchAsync(async (req, res) => {
const { email, password } = req.body;
const user = await authService.loginUserWithEmailAndPassword(email, password);
const tokens = await tokenService.generateAuthTokens(user);
res.send({ user, tokens });
});
const logout = catchAsync(async (req, res) => {
await authService.logout(req.body.refreshToken);
res.status(httpStatus.NO_CONTENT).send();
});
const refreshTokens = catchAsync(async (req, res) => {
const tokens = await authService.refreshAuth(req.body.refreshToken);
res.send({ ...tokens });
});
const forgotPassword = catchAsync(async (req, res) => {
const resetPasswordToken = await tokenService.generateResetPasswordToken(req.body.email);
await emailService.sendResetPasswordEmail(req.body.email, resetPasswordToken);
res.status(httpStatus.NO_CONTENT).send();
});
const resetPassword = catchAsync(async (req, res) => {
await authService.resetPassword(req.query.token, req.body.password);
res.status(httpStatus.NO_CONTENT).send();
});
const sendVerificationEmail = catchAsync(async (req, res) => {
const verifyEmailToken = await tokenService.generateVerifyEmailToken(req.user);
await emailService.sendVerificationEmail(req.user.email, verifyEmailToken);
res.status(httpStatus.NO_CONTENT).send();
});
const verifyEmail = catchAsync(async (req, res) => {
await authService.verifyEmail(req.query.token);
res.status(httpStatus.NO_CONTENT).send();
});
module.exports = {
register,
login,
logout,
refreshTokens,
forgotPassword,
resetPassword,
sendVerificationEmail,
verifyEmail,
};

2
src/controllers/index.js Normal file
View File

@@ -0,0 +1,2 @@
module.exports.authController = require('./auth.controller');
module.exports.userController = require('./user.controller');

View File

@@ -0,0 +1,43 @@
const httpStatus = require('http-status');
const pick = require('../utils/pick');
const ApiError = require('../utils/ApiError');
const catchAsync = require('../utils/catchAsync');
const { userService } = require('../services');
const createUser = catchAsync(async (req, res) => {
const user = await userService.createUser(req.body);
res.status(httpStatus.CREATED).send(user);
});
const getUsers = catchAsync(async (req, res) => {
const filter = pick(req.query, ['name', 'role']);
const options = pick(req.query, ['sortBy', 'limit', 'page']);
const result = await userService.queryUsers(filter, options);
res.send(result);
});
const getUser = catchAsync(async (req, res) => {
const user = await userService.getUserById(req.params.userId);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
}
res.send(user);
});
const updateUser = catchAsync(async (req, res) => {
const user = await userService.updateUserById(req.params.userId, req.body);
res.send(user);
});
const deleteUser = catchAsync(async (req, res) => {
await userService.deleteUserById(req.params.userId);
res.status(httpStatus.NO_CONTENT).send();
});
module.exports = {
createUser,
getUsers,
getUser,
updateUser,
deleteUser,
};

92
src/docs/components.yml Normal file
View File

@@ -0,0 +1,92 @@
components:
schemas:
User:
type: object
properties:
id:
type: string
email:
type: string
format: email
name:
type: string
role:
type: string
enum: [user, admin]
example:
id: 5ebac534954b54139806c112
email: fake@example.com
name: fake name
role: user
Token:
type: object
properties:
token:
type: string
expires:
type: string
format: date-time
example:
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg
expires: 2020-05-12T16:18:04.793Z
AuthTokens:
type: object
properties:
access:
$ref: '#/components/schemas/Token'
refresh:
$ref: '#/components/schemas/Token'
Error:
type: object
properties:
code:
type: number
message:
type: string
responses:
DuplicateEmail:
description: Email already taken
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
code: 400
message: Email already taken
Unauthorized:
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
code: 401
message: Please authenticate
Forbidden:
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
code: 403
message: Forbidden
NotFound:
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
code: 404
message: Not found
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT

21
src/docs/swaggerDef.js Normal file
View File

@@ -0,0 +1,21 @@
const { version } = require('../../package.json');
const config = require('../config/config');
const swaggerDef = {
openapi: '3.0.0',
info: {
title: 'node-express-boilerplate API documentation',
version,
license: {
name: 'MIT',
url: 'https://github.com/hagopj13/node-express-boilerplate/blob/master/LICENSE',
},
},
servers: [
{
url: `http://localhost:${config.port}/v1`,
},
],
};
module.exports = swaggerDef;

5
src/handlers/pingTest.js Normal file
View File

@@ -0,0 +1,5 @@
const homeHandler = (req, res) => {
res.send("Pong");
};
export default homeHandler;

38
src/index.js Normal file
View File

@@ -0,0 +1,38 @@
const mongoose = require('mongoose');
const app = require('./app');
const config = require('./config/config');
const logger = require('./config/logger');
let server;
mongoose.connect(config.mongoose.url, config.mongoose.options).then(() => {
logger.info('Connected to MongoDB');
server = app.listen(config.port, () => {
logger.info(`Listening to port ${config.port}`);
});
});
const exitHandler = () => {
if (server) {
server.close(() => {
logger.info('Server closed');
process.exit(1);
});
} else {
process.exit(1);
}
};
const unexpectedErrorHandler = (error) => {
logger.error(error);
exitHandler();
};
process.on('uncaughtException', unexpectedErrorHandler);
process.on('unhandledRejection', unexpectedErrorHandler);
process.on('SIGTERM', () => {
logger.info('SIGTERM received');
if (server) {
server.close();
}
});

31
src/middlewares/auth.js Normal file
View File

@@ -0,0 +1,31 @@
const passport = require('passport');
const httpStatus = require('http-status');
const ApiError = require('../utils/ApiError');
const { roleRights } = require('../config/roles');
const verifyCallback = (req, resolve, reject, requiredRights) => async (err, user, info) => {
if (err || info || !user) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
}
req.user = user;
if (requiredRights.length) {
const userRights = roleRights.get(user.role);
const hasRequiredRights = requiredRights.every((requiredRight) => userRights.includes(requiredRight));
if (!hasRequiredRights && req.params.userId !== user.id) {
return reject(new ApiError(httpStatus.FORBIDDEN, 'Forbidden'));
}
}
resolve();
};
const auth = (...requiredRights) => async (req, res, next) => {
return new Promise((resolve, reject) => {
passport.authenticate('jwt', { session: false }, verifyCallback(req, resolve, reject, requiredRights))(req, res, next);
})
.then(() => next())
.catch((err) => next(err));
};
module.exports = auth;

44
src/middlewares/error.js Normal file
View File

@@ -0,0 +1,44 @@
const mongoose = require('mongoose');
const httpStatus = require('http-status');
const config = require('../config/config');
const logger = require('../config/logger');
const ApiError = require('../utils/ApiError');
const errorConverter = (err, req, res, next) => {
let error = err;
if (!(error instanceof ApiError)) {
const statusCode =
error.statusCode || error instanceof mongoose.Error ? httpStatus.BAD_REQUEST : httpStatus.INTERNAL_SERVER_ERROR;
const message = error.message || httpStatus[statusCode];
error = new ApiError(statusCode, message, false, err.stack);
}
next(error);
};
// eslint-disable-next-line no-unused-vars
const errorHandler = (err, req, res, next) => {
let { statusCode, message } = err;
if (config.env === 'production' && !err.isOperational) {
statusCode = httpStatus.INTERNAL_SERVER_ERROR;
message = httpStatus[httpStatus.INTERNAL_SERVER_ERROR];
}
res.locals.errorMessage = err.message;
const response = {
code: statusCode,
message,
...(config.env === 'development' && { stack: err.stack }),
};
if (config.env === 'development') {
logger.error(err);
}
res.status(statusCode).send(response);
};
module.exports = {
errorConverter,
errorHandler,
};

View File

@@ -0,0 +1,11 @@
const rateLimit = require('express-rate-limit');
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 20,
skipSuccessfulRequests: true,
});
module.exports = {
authLimiter,
};

View File

@@ -0,0 +1,21 @@
const Joi = require('joi');
const httpStatus = require('http-status');
const pick = require('../utils/pick');
const ApiError = require('../utils/ApiError');
const validate = (schema) => (req, res, next) => {
const validSchema = pick(schema, ['params', 'query', 'body']);
const object = pick(req, Object.keys(validSchema));
const { value, error } = Joi.compile(validSchema)
.prefs({ errors: { label: 'key' }, abortEarly: false })
.validate(object);
if (error) {
const errorMessage = error.details.map((details) => details.message).join(', ');
return next(new ApiError(httpStatus.BAD_REQUEST, errorMessage));
}
Object.assign(req, value);
return next();
};
module.exports = validate;

2
src/models/index.js Normal file
View File

@@ -0,0 +1,2 @@
module.exports.Token = require('./token.model');
module.exports.User = require('./user.model');

View File

@@ -0,0 +1,2 @@
module.exports.toJSON = require('./toJSON.plugin');
module.exports.paginate = require('./paginate.plugin');

View File

@@ -0,0 +1,70 @@
/* eslint-disable no-param-reassign */
const paginate = (schema) => {
/**
* @typedef {Object} QueryResult
* @property {Document[]} results - Results found
* @property {number} page - Current page
* @property {number} limit - Maximum number of results per page
* @property {number} totalPages - Total number of pages
* @property {number} totalResults - Total number of documents
*/
/**
* Query for documents with pagination
* @param {Object} [filter] - Mongo filter
* @param {Object} [options] - Query options
* @param {string} [options.sortBy] - Sorting criteria using the format: sortField:(desc|asc). Multiple sorting criteria should be separated by commas (,)
* @param {string} [options.populate] - Populate data fields. Hierarchy of fields should be separated by (.). Multiple populating criteria should be separated by commas (,)
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
* @param {number} [options.page] - Current page (default = 1)
* @returns {Promise<QueryResult>}
*/
schema.statics.paginate = async function (filter, options) {
let sort = '';
if (options.sortBy) {
const sortingCriteria = [];
options.sortBy.split(',').forEach((sortOption) => {
const [key, order] = sortOption.split(':');
sortingCriteria.push((order === 'desc' ? '-' : '') + key);
});
sort = sortingCriteria.join(' ');
} else {
sort = 'createdAt';
}
const limit = options.limit && parseInt(options.limit, 10) > 0 ? parseInt(options.limit, 10) : 10;
const page = options.page && parseInt(options.page, 10) > 0 ? parseInt(options.page, 10) : 1;
const skip = (page - 1) * limit;
const countPromise = this.countDocuments(filter).exec();
let docsPromise = this.find(filter).sort(sort).skip(skip).limit(limit);
if (options.populate) {
options.populate.split(',').forEach((populateOption) => {
docsPromise = docsPromise.populate(
populateOption
.split('.')
.reverse()
.reduce((a, b) => ({ path: b, populate: a }))
);
});
}
docsPromise = docsPromise.exec();
return Promise.all([countPromise, docsPromise]).then((values) => {
const [totalResults, results] = values;
const totalPages = Math.ceil(totalResults / limit);
const result = {
results,
page,
limit,
totalPages,
totalResults,
};
return Promise.resolve(result);
});
};
};
module.exports = paginate;

View File

@@ -0,0 +1,43 @@
/* eslint-disable no-param-reassign */
/**
* A mongoose schema plugin which applies the following in the toJSON transform call:
* - removes __v, createdAt, updatedAt, and any path that has private: true
* - replaces _id with id
*/
const deleteAtPath = (obj, path, index) => {
if (index === path.length - 1) {
delete obj[path[index]];
return;
}
deleteAtPath(obj[path[index]], path, index + 1);
};
const toJSON = (schema) => {
let transform;
if (schema.options.toJSON && schema.options.toJSON.transform) {
transform = schema.options.toJSON.transform;
}
schema.options.toJSON = Object.assign(schema.options.toJSON || {}, {
transform(doc, ret, options) {
Object.keys(schema.paths).forEach((path) => {
if (schema.paths[path].options && schema.paths[path].options.private) {
deleteAtPath(ret, path.split('.'), 0);
}
});
ret.id = ret._id.toString();
delete ret._id;
delete ret.__v;
delete ret.createdAt;
delete ret.updatedAt;
if (transform) {
return transform(doc, ret, options);
}
},
});
};
module.exports = toJSON;

44
src/models/token.model.js Normal file
View File

@@ -0,0 +1,44 @@
const mongoose = require('mongoose');
const { toJSON } = require('./plugins');
const { tokenTypes } = require('../config/tokens');
const tokenSchema = mongoose.Schema(
{
token: {
type: String,
required: true,
index: true,
},
user: {
type: mongoose.SchemaTypes.ObjectId,
ref: 'User',
required: true,
},
type: {
type: String,
enum: [tokenTypes.REFRESH, tokenTypes.RESET_PASSWORD, tokenTypes.VERIFY_EMAIL],
required: true,
},
expires: {
type: Date,
required: true,
},
blacklisted: {
type: Boolean,
default: false,
},
},
{
timestamps: true,
}
);
// add plugin that converts mongoose to json
tokenSchema.plugin(toJSON);
/**
* @typedef Token
*/
const Token = mongoose.model('Token', tokenSchema);
module.exports = Token;

91
src/models/user.model.js Normal file
View File

@@ -0,0 +1,91 @@
const mongoose = require('mongoose');
const validator = require('validator');
const bcrypt = require('bcryptjs');
const { toJSON, paginate } = require('./plugins');
const { roles } = require('../config/roles');
const userSchema = mongoose.Schema(
{
name: {
type: String,
required: true,
trim: true,
},
email: {
type: String,
required: true,
unique: true,
trim: true,
lowercase: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error('Invalid email');
}
},
},
password: {
type: String,
required: true,
trim: true,
minlength: 8,
validate(value) {
if (!value.match(/\d/) || !value.match(/[a-zA-Z]/)) {
throw new Error('Password must contain at least one letter and one number');
}
},
private: true, // used by the toJSON plugin
},
role: {
type: String,
enum: roles,
default: 'user',
},
isEmailVerified: {
type: Boolean,
default: false,
},
},
{
timestamps: true,
}
);
// add plugin that converts mongoose to json
userSchema.plugin(toJSON);
userSchema.plugin(paginate);
/**
* Check if email is taken
* @param {string} email - The user's email
* @param {ObjectId} [excludeUserId] - The id of the user to be excluded
* @returns {Promise<boolean>}
*/
userSchema.statics.isEmailTaken = async function (email, excludeUserId) {
const user = await this.findOne({ email, _id: { $ne: excludeUserId } });
return !!user;
};
/**
* Check if password matches the user's password
* @param {string} password
* @returns {Promise<boolean>}
*/
userSchema.methods.isPasswordMatch = async function (password) {
const user = this;
return bcrypt.compare(password, user.password);
};
userSchema.pre('save', async function (next) {
const user = this;
if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 8);
}
next();
});
/**
* @typedef User
*/
const User = mongoose.model('User', userSchema);
module.exports = User;

17
src/routes/api/apiTest.js Normal file
View File

@@ -0,0 +1,17 @@
const apiTest = (req, res) => {
// res.send(req.query.doa); //get
// res.send(req.body.doa); //post
const responseObject = {
message: 'Hello, this is your JSON response!',
success: true,
data: {
name: 'John Doe',
age: 30,
job: 'Developer'
}
};
res.json(responseObject);
};
module.exports = apiTest

View File

@@ -0,0 +1,149 @@
const classMates = (req, res) => {
// res.send(req.query.doa); //get
// res.send(req.body.doa); //post
let classmatesData = [
{
id: "1",
name: "Daniel Nguyen",
program: "Graduate Program",
type: "Student",
avatar: "/assets/avatar1.png"
},
{
id: "2",
name: "Sarah Anderson",
program: "Post-Graduate Program",
type: "Student",
avatar: "/assets/avatar2.png"
},
{
id: "3",
name: "John Smith",
program: "Undergraduate Program",
type: "Student",
avatar: "/assets/avatar3.png"
},
{
id: "4",
name: "Emily Davis",
program: "Graduate Program",
type: "Student",
avatar: "/assets/avatar4.png"
},
{
id: "5",
name: "Michael Johnson",
program: "Post-Graduate Program",
type: "Student",
avatar: "/assets/avatar5.png"
},
{
id: "6",
name: "Jessica Wilson",
program: "Undergraduate Program",
type: "Student",
avatar: "/assets/avatar6.png"
},
{
id: "7",
name: "David Brown",
program: "Graduate Program",
type: "Student",
avatar: "/assets/avatar1.png"
},
{
id: "8",
name: "Laura Lee",
program: "Post-Graduate Program",
type: "Student",
avatar: "/assets/avatar2.png"
},
{
id: "9",
name: "Chris Miller",
program: "Undergraduate Program",
type: "Student",
avatar: "/assets/avatar3.png"
},
{
id: "10",
name: "Sophia Taylor",
program: "Graduate Program",
type: "Student",
avatar: "/assets/avatar4.png"
},
{
id: "11",
name: "James Anderson",
program: "Post-Graduate Program",
type: "Student",
avatar: "/assets/avatar5.png"
},
{
id: "12",
name: "Olivia Thomas",
program: "Undergraduate Program",
type: "Student",
avatar: "/assets/avatar6.png"
},
{
id: "13",
name: "Ethan Martinez",
program: "Graduate Program",
type: "Student",
avatar: "/assets/avatar1.png"
},
{
id: "14",
name: "Ava Garcia",
program: "Post-Graduate Program",
type: "Student",
avatar: "/assets/avatar2.png"
},
{
id: "15",
name: "Noah Rodriguez",
program: "Undergraduate Program",
type: "Student",
avatar: "/assets/avatar3.png"
},
{
id: "16",
name: "Mia Martinez",
program: "Graduate Program",
type: "Student",
avatar: "/assets/avatar4.png"
},
{
id: "17",
name: "Lucas Wilson",
program: "Post-Graduate Program",
type: "Student",
avatar: "/assets/avatar5.png"
},
{
id: "18",
name: "Isabella Clark",
program: "Undergraduate Program",
type: "Student",
avatar: "/assets/avatar6.png"
},
{
id: "19",
name: "Liam Walker",
program: "Graduate Program",
type: "Student",
avatar: "/assets/avatar1.png"
},
{
id: "20",
name: "Charlotte Lewis",
program: "Post-Graduate Program",
type: "Student",
avatar: "/assets/avatar2.png"
}
];
res.json(classmatesData);
};
module.exports = classMates;

View File

@@ -0,0 +1,70 @@
const mysql = require("mysql2");
const continueLearning = (req, res) => {
// res.send(req.query.doa); //get
// res.send(req.body.doa); //post
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if(err) {
console.error('Error connecting to the database:', err);
return;
}
console.log('Connected to the MariaDB database.');
});
const data = req.body;
const query = `SELECT * FROM continue_learning`;
connection.query(query, (err, results) => {
if (err) {
console.error('Error inserting data:', err);
res.status(500).send('Internal Server Error');
return;
}
res.status(200).json(results);
});
};
module.exports = continueLearning;
// let courseData = [
// {
// id : "1",
// title : "Life History of Dr. Maria Montessori",
// chapter : "1",
// Program : "Graduate Program",
// img : "/assets/course1.jpg"
// },
// {
// id : "2",
// title : "Introduction to Montessori Methods",
// chapter : "2",
// Program : "Graduate Program",
// img : "/assets/course2.jpg"
// },
// {
// id : "3",
// title : "Exercises on Practical Life",
// chapter : "3",
// Program : "Graduate Program",
// img : "/assets/course3.jpg"
// }
// ];
// res.json(courseData);

View File

@@ -0,0 +1,35 @@
const mysql = require("mysql2");
const generateQuestions = (req, res) => {
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if (err) {
console.error('Error connecting to the database:', err);
return;
}
console.log('Connected to the MariaDB database.');
});
const { questions } = req.body;
res.send( questions );
questions.forEach(question => {
const { questionText, options, correctAnswer } = question;
const sql = 'INSERT INTO quiz_questions (questionText, option1, option2, option3, option4, correctAnswer) VALUES (?, ?, ?, ?, ?, ?)';
db.query(sql, [questionText, options[0], options[1], options[2], options[3], correctAnswer], (err, result) => {
if (err) {
console.error('Error inserting question:', err);
res.status(500).send('Error inserting question');
return;
}
});
});
res.status(200).send('Questions added successfully');
}
module.exports = generateQuestions;

View File

@@ -0,0 +1,32 @@
const mysql = require("mysql2");
const getGameScore = (req, res) => {
const pool = mysql.createPool({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: 'beanstalk_game',
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
const promisePool = pool.promise();
const { userId, gameName, gameID } = req.body;
const query = 'SELECT score, gameTime FROM gameData WHERE userId = ? AND gameName = ? AND gameID = ?';
pool.query(query, [userId, gameName, gameID], (error, results) => {
if (error) {
return res.status(500).json({ message: 'Database query failed', error });
}
if (results.length > 0) {
const game = results[0];
res.json({ score: game.score, gameTime: game.gameTime });
} else {
res.status(404).json({ message: 'Game data not found' });
}
});
};
module.exports = getGameScore;

View File

@@ -0,0 +1,63 @@
const mysql = require("mysql2");
const knowledgeQuests = (req, res) => {
// res.send(req.query.doa); //get
// res.send(req.body.doa); //post
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if(err) {
console.error('Error connecting to the database:', err);
return;
}
console.log('Connected to the MariaDB database.');
});
const data = req.body;
const query = `SELECT * FROM knowledge_quests WHERE status = 1`;
connection.query(query, (err, results) => {
if (err) {
console.error('Error inserting data:', err);
res.status(500).send('Internal Server Error');
return;
}
res.status(200).json(results);
});
};
module.exports = knowledgeQuests;
// let knowledgeData = [
// {
// id: "1",
// status: "1",
// title: "Assessment on Special Education",
// challenge: "Challenge yourself & climb the leaderboard.",
// question: "Subjective Question",
// img: "/assets/knowledge1.jpg"
// },
// {
// id: "2",
// status: "1",
// title: "Quiz on Children Psychology",
// challenge: "Challenge yourself & climb the leaderboard.",
// question: "MCQ",
// img: "/assets/knowledge2.jpg"
// },
// {
// id: "3",
// status: "1",
// title: "Quiz on Montessori Methods",
// challenge: "Challenge yourself & climb the leaderboard.",
// question: "MCQ",
// img: "/assets/knowledge3.jpg"
// }
// ];
// res.json(knowledgeData);

View File

@@ -0,0 +1,180 @@
const knowledgeQuestsCompleted = (req, res) => {
// res.send(req.query.doa); //get
// res.send(req.body.doa); //post
let knowledgeCompleted = [
{
id: "1",
status: "1",
title: "Assessment on Special Education",
challenge: "Challenge yourself & climb the leaderboard.",
question: "Subjective Question",
img: "/assets/knowledge1.jpg"
},
{
id: "2",
status: "1",
title: "Quiz on Children Psychology",
challenge: "Challenge yourself & climb the leaderboard.",
question: "MCQ",
img: "/assets/knowledge2.jpg"
},
{
id: "3",
status: "1",
title: "Quiz on Montessori Methods",
challenge: "Challenge yourself & climb the leaderboard.",
question: "MCQ",
img: "/assets/knowledge3.jpg"
},
{
id: "4",
status: "1",
title: "Assessment on Special Education",
challenge: "Challenge yourself & climb the leaderboard.",
question: "Subjective Question",
img: "/assets/knowledge1.jpg"
},
{
id: "5",
status: "1",
title: "Quiz on Children Psychology",
challenge: "Challenge yourself & climb the leaderboard.",
question: "MCQ",
img: "/assets/knowledge2.jpg"
},
{
id: "6",
status: "1",
title: "Quiz on Montessori Methods",
challenge: "Challenge yourself & climb the leaderboard.",
question: "MCQ",
img: "/assets/knowledge3.jpg"
},
{
id: "7",
status: "1",
title: "Workshop on Child Development",
challenge: "Expand your knowledge & earn badges.",
question: "Interactive Session",
img: "/assets/knowledge1.jpg"
},
{
id: "8",
status: "1",
title: "Webinar on Educational Psychology",
challenge: "Join & enhance your skills.",
question: "Discussion",
img: "/assets/knowledge2.jpg"
},
{
id: "9",
status: "1",
title: "Seminar on Inclusive Education",
challenge: "Participate & gain insights.",
question: "Lecture",
img: "/assets/knowledge3.jpg"
},
{
id: "10",
status: "1",
title: "Course on Early Childhood Education",
challenge: "Complete the course & get certified.",
question: "Multiple Modules",
img: "/assets/knowledge1.jpg"
},
{
id: "11",
status: "1",
title: "Training on Classroom Management",
challenge: "Improve your teaching strategies.",
question: "Practical Tasks",
img: "/assets/knowledge2.jpg"
},
{
id: "12",
status: "1",
title: "Lecture on Cognitive Development",
challenge: "Expand your understanding & get certified.",
question: "Q&A Session",
img: "/assets/knowledge3.jpg"
},
{
id: "13",
status: "1",
title: "Workshop on Behavioral Issues",
challenge: "Join & learn from experts.",
question: "Interactive Session",
img: "/assets/knowledge1.jpg"
},
{
id: "14",
status: "1",
title: "Seminar on Learning Disabilities",
challenge: "Participate & enhance your knowledge.",
question: "Lecture",
img: "/assets/knowledge2.jpg"
},
{
id: "15",
status: "1",
title: "Webinar on Child Psychology",
challenge: "Join & expand your skills.",
question: "Discussion",
img: "/assets/knowledge3.jpg"
},
{
id: "16",
status: "1",
title: "Course on Special Education Needs",
challenge: "Complete the course & get certified.",
question: "Multiple Modules",
img: "/assets/knowledge1.jpg"
},
{
id: "17",
status: "1",
title: "Training on Autism Spectrum Disorder",
challenge: "Improve your teaching strategies.",
question: "Practical Tasks",
img: "/assets/knowledge2.jpg"
},
{
id: "18",
status: "1",
title: "Lecture on Emotional Development",
challenge: "Expand your understanding & get certified.",
question: "Q&A Session",
img: "/assets/knowledge3.jpg"
},
{
id: "19",
status: "1",
title: "Workshop on ADHD",
challenge: "Join & learn from experts.",
question: "Interactive Session",
img: "/assets/knowledge1.jpg"
},
{
id: "20",
status: "1",
title: "Seminar on Speech and Language Disorders",
challenge: "Participate & enhance your knowledge.",
question: "Lecture",
img: "/assets/knowledge2.jpg"
},
{
id: "21",
status: "1",
title: "Webinar on Child Nutrition",
challenge: "Join & expand your skills.",
question: "Discussion",
img: "/assets/knowledge3.jpg"
}
];
res.json(knowledgeCompleted);
};
module.exports = knowledgeQuestsCompleted;
// knowledgeQuestsAllContent

View File

@@ -0,0 +1,180 @@
const knowledgeQuestsAllContent = (req, res) => {
// res.send(req.query.doa); //get
// res.send(req.body.doa); //post
let knowledgeData = [
{
id: "1",
status: "1",
title: "Assessment on Special Education",
challenge: "Challenge yourself & climb the leaderboard.",
question: "Subjective Question",
img: "/assets/knowledge1.jpg"
},
{
id: "2",
status: "1",
title: "Quiz on Children Psychology",
challenge: "Challenge yourself & climb the leaderboard.",
question: "MCQ",
img: "/assets/knowledge2.jpg"
},
{
id: "3",
status: "1",
title: "Quiz on Montessori Methods",
challenge: "Challenge yourself & climb the leaderboard.",
question: "MCQ",
img: "/assets/knowledge3.jpg"
},
{
id: "4",
status: "1",
title: "Assessment on Special Education",
challenge: "Challenge yourself & climb the leaderboard.",
question: "Subjective Question",
img: "/assets/knowledge1.jpg"
},
{
id: "5",
status: "1",
title: "Quiz on Children Psychology",
challenge: "Challenge yourself & climb the leaderboard.",
question: "MCQ",
img: "/assets/knowledge2.jpg"
},
{
id: "6",
status: "1",
title: "Quiz on Montessori Methods",
challenge: "Challenge yourself & climb the leaderboard.",
question: "MCQ",
img: "/assets/knowledge3.jpg"
},
{
id: "7",
status: "1",
title: "Workshop on Child Development",
challenge: "Expand your knowledge & earn badges.",
question: "Interactive Session",
img: "/assets/knowledge1.jpg"
},
{
id: "8",
status: "1",
title: "Webinar on Educational Psychology",
challenge: "Join & enhance your skills.",
question: "Discussion",
img: "/assets/knowledge2.jpg"
},
{
id: "9",
status: "1",
title: "Seminar on Inclusive Education",
challenge: "Participate & gain insights.",
question: "Lecture",
img: "/assets/knowledge3.jpg"
},
{
id: "10",
status: "1",
title: "Course on Early Childhood Education",
challenge: "Complete the course & get certified.",
question: "Multiple Modules",
img: "/assets/knowledge1.jpg"
},
{
id: "11",
status: "1",
title: "Training on Classroom Management",
challenge: "Improve your teaching strategies.",
question: "Practical Tasks",
img: "/assets/knowledge2.jpg"
},
{
id: "12",
status: "1",
title: "Lecture on Cognitive Development",
challenge: "Expand your understanding & get certified.",
question: "Q&A Session",
img: "/assets/knowledge3.jpg"
},
{
id: "13",
status: "1",
title: "Workshop on Behavioral Issues",
challenge: "Join & learn from experts.",
question: "Interactive Session",
img: "/assets/knowledge1.jpg"
},
{
id: "14",
status: "1",
title: "Seminar on Learning Disabilities",
challenge: "Participate & enhance your knowledge.",
question: "Lecture",
img: "/assets/knowledge2.jpg"
},
{
id: "15",
status: "1",
title: "Webinar on Child Psychology",
challenge: "Join & expand your skills.",
question: "Discussion",
img: "/assets/knowledge3.jpg"
},
{
id: "16",
status: "1",
title: "Course on Special Education Needs",
challenge: "Complete the course & get certified.",
question: "Multiple Modules",
img: "/assets/knowledge1.jpg"
},
{
id: "17",
status: "1",
title: "Training on Autism Spectrum Disorder",
challenge: "Improve your teaching strategies.",
question: "Practical Tasks",
img: "/assets/knowledge2.jpg"
},
{
id: "18",
status: "1",
title: "Lecture on Emotional Development",
challenge: "Expand your understanding & get certified.",
question: "Q&A Session",
img: "/assets/knowledge3.jpg"
},
{
id: "19",
status: "1",
title: "Workshop on ADHD",
challenge: "Join & learn from experts.",
question: "Interactive Session",
img: "/assets/knowledge1.jpg"
},
{
id: "20",
status: "1",
title: "Seminar on Speech and Language Disorders",
challenge: "Participate & enhance your knowledge.",
question: "Lecture",
img: "/assets/knowledge2.jpg"
},
{
id: "21",
status: "1",
title: "Webinar on Child Nutrition",
challenge: "Join & expand your skills.",
question: "Discussion",
img: "/assets/knowledge3.jpg"
}
];
res.json(knowledgeData);
};
module.exports = knowledgeQuestsAllContent;
// knowledgeQuestsAllContent

View File

@@ -0,0 +1,34 @@
const mysql = require("mysql2");
const newQuestion = (req, res) => {
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if (err) {
console.error('Error connecting to the database:', err);
return;
}
console.log('Connected to the MariaDB database.');
});
const data = req.body;
const query = `INSERT INTO quiz_questions (questionText, option1, option2, option3, option4, correctAnswer, moduleId) VALUES (?, ?, ?, ?, ?, ?, ?)`;
const values = [data.question, data.option1, data.option2, data.option3, data.option4, data.correctAnswer, data.moduleId];
connection.query(query, values, (err, results) => {
if (err) {
console.error('Error inserting data:', err);
res.status(500).send('Internal Server Error');
return;
}
res.status(200).json(results);
});
}
module.exports = newQuestion
;

34
src/routes/api/newQuiz.js Normal file
View File

@@ -0,0 +1,34 @@
const mysql = require("mysql2");
const newQuiz = (req, res) => {
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if (err) {
console.error('Error connecting to the database:', err);
return;
}
console.log('Connected to the MariaDB database.');
});
const data = req.body;
const query = `INSERT INTO quiz (moduleName, type) VALUES (?, ?)`;
const values = [data.moduleName, data.moduleType];
connection.query(query, values, (err, results) => {
if (err) {
console.error('Error inserting data:', err);
res.status(500).send('Internal Server Error');
return;
}
res.status(200).json(results);
});
}
module.exports = newQuiz
;

View File

@@ -0,0 +1,182 @@
const mysql = require("mysql2");
const questionList = (req, res) => {
// res.send(req.query.doa); //get
// res.send(req.body.doa); //post
function generateUniqueId() {
let timestamp = new Date().getTime().toString();
let randomNumber = Math.floor(Math.random() * 100000).toString();
let uniqueId = timestamp + randomNumber;
if (uniqueId.length > 10) {
uniqueId = uniqueId.substring(0, 10);
} else if (uniqueId.length < 10) {
while (uniqueId.length < 10) {
uniqueId += Math.floor(Math.random() * 10).toString();
}
}
return uniqueId;
}
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if(err) {
console.error('Error connecting to the database:', err);
return;
}
console.log('Connected to the MariaDB database.');
});
const data = req.body;
const query = `SELECT * FROM quiz_questions LIMIT 6`;
connection.query(query, (err, results) => {
if (err) {
console.error('Error inserting data:', err);
res.status(500).send('Internal Server Error');
return;
}
const uniqueId = generateUniqueId();
results.forEach(obj => {
obj.newQuizId = uniqueId
})
res.status(200).json(results);
});
};
module.exports = questionList;
// {
// id: 1,
// question: "What is the capital of France?",
// options: ["Berlin", "Madrid", "Paris", "Rome"],
// answer: "Paris",
// },
// {
// id: 2,
// question: "Which planet is known as the Red Planet?",
// options: ["Earth", "Mars", "Jupiter", "Saturn"],
// answer: "Mars",
// },
// {
// id: 3,
// question: "What is the chemical symbol for gold?",
// options: ["Au", "Ag", "Pb", "Fe"],
// answer: "Au",
// },
// {
// id: 4,
// question: "Who wrote 'To Kill a Mockingbird'?",
// options: ["Harper Lee", "Mark Twain", "Ernest Hemingway", "J.K. Rowling"],
// answer: "Harper Lee",
// },
// {
// id: 5,
// question: "What is the largest ocean on Earth?",
// options: ["Atlantic Ocean", "Indian Ocean", "Arctic Ocean", "Pacific Ocean"],
// answer: "Pacific Ocean",
// },
// {
// id: 6,
// question: "Which element has the atomic number 1?",
// options: ["Helium", "Hydrogen", "Oxygen", "Carbon"],
// answer: "Hydrogen",
// },
// {
// id: 7,
// question: "In which year did the Titanic sink?",
// options: ["1912", "1905", "1898", "1923"],
// answer: "1912",
// },
// {
// id: 8,
// question: "Who is the author of '1984'?",
// options: ["George Orwell", "Aldous Huxley", "Ray Bradbury", "J.D. Salinger"],
// answer: "George Orwell",
// },
// {
// id: 9,
// question: "What is the hardest natural substance on Earth?",
// options: ["Gold", "Platinum", "Diamond", "Iron"],
// answer: "Diamond",
// },
// {
// id: 10,
// question: "What is the largest planet in our solar system?",
// options: ["Earth", "Saturn", "Neptune", "Jupiter"],
// answer: "Jupiter",
// },
// {
// id: 11,
// question: "What is the main ingredient in guacamole?",
// options: ["Tomato", "Avocado", "Pepper", "Onion"],
// answer: "Avocado",
// },
// {
// id: 12,
// question: "Which country is known as the Land of the Rising Sun?",
// options: ["China", "Japan", "Thailand", "South Korea"],
// answer: "Japan",
// },
// {
// id: 13,
// question: "What is the smallest prime number?",
// options: ["1", "2", "3", "5"],
// answer: "2",
// },
// {
// id: 14,
// question: "Who painted the Mona Lisa?",
// options: ["Vincent van Gogh", "Leonardo da Vinci", "Pablo Picasso", "Claude Monet"],
// answer: "Leonardo da Vinci",
// },
// {
// id: 15,
// question: "What is the capital city of Australia?",
// options: ["Sydney", "Melbourne", "Canberra", "Brisbane"],
// answer: "Canberra",
// },
// {
// id: 16,
// question: "Which gas do plants primarily use for photosynthesis?",
// options: ["Oxygen", "Nitrogen", "Carbon Dioxide", "Hydrogen"],
// answer: "Carbon Dioxide",
// },
// {
// id: 17,
// question: "What is the boiling point of water in Celsius?",
// options: ["90°C", "100°C", "110°C", "120°C"],
// answer: "100°C",
// },
// {
// id: 18,
// question: "Which language is primarily spoken in Brazil?",
// options: ["Spanish", "Portuguese", "French", "English"],
// answer: "Portuguese",
// },
// {
// id: 19,
// question: "What is the smallest unit of life?",
// options: ["Tissue", "Organ", "Cell", "Organism"],
// answer: "Cell",
// },
// {
// id: 20,
// question: "Who developed the theory of relativity?",
// options: ["Isaac Newton", "Galileo Galilei", "Albert Einstein", "Niels Bohr"],
// answer: "Albert Einstein",
// },
// {
// id: 21,
// question: "In what year did World War II end?",
// options: ["1945", "1944", "1946", "1943"],
// answer: "1945",
// },
// ];

View File

@@ -0,0 +1,33 @@
const mysql = require("mysql2");
const quizList = (req, res) => {
// res.send(req.query.doa); //get
// res.send(req.body.doa); //post
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if(err) {
console.error('Error connecting to the database:', err);
return;
}
console.log('Connected to the MariaDB database.');
});
const data = req.body;
const query = `SELECT * FROM quiz`;
connection.query(query, (err, results) => {
if (err) {
console.error('Error inserting data:', err);
res.status(500).send('Internal Server Error');
return;
}
res.status(200).json(results);
});
};
module.exports = quizList;

View File

@@ -0,0 +1,419 @@
const mysql = require("mysql2");
const quizModuleData = (req, res) => {
// res.send(req.query.doa); //get
// res.send(req.body.doa); //post
// const connection = mysql.createConnection({
// host: process.env.MARIA_HOST,
// user: process.env.MARIA_USER,
// password: process.env.MARIA_PASS,
// database: process.env.MARIA_DBNM
// });
// connection.connect((err) => {
// if(err) {
// console.error('Error connecting to the database:', err);
// return;
// }
// console.log('Connected to the MariaDB database.');
// });
// const data = req.body;
// const query = `SELECT * FROM quiz_questions`;
// connection.query(query, (err, results) => {
// if (err) {
// console.error('Error inserting data:', err);
// res.status(500).send('Internal Server Error');
// return;
// }
// res.status(200).json(results);
// });
let quizModuleData = {
modules: [
{
moduleId: 1,
type: "Theory Quiz Scores",
moduleName: "Module 1 - Life History of Dr. Maria Montessori",
quizzes: [
{
quizId: 1,
quizName: "Lorem Ipsum Dolor Sit",
attendQuestion: 48,
totalQuestion: 50,
internalMarks: 28,
attendance: 20,
questions: [
{
questionId: 1,
questionText: "What is the capital of France?",
options: [
"Paris",
"London",
"Berlin",
"Madrid"
],
correctAnswer: "Paris"
},
{
questionId: 2,
questionText: "What is 2 + 2?",
options: [
"3",
"4",
"5",
"6"
],
correctAnswer: "4"
},
{
questionId: 3,
questionText: "What is the boiling point of water?",
options: [
"90°C",
"100°C",
"110°C",
"120°C"
],
correctAnswer: "100°C"
},
{
questionId: 4,
questionText: "Who wrote 'To Kill a Mockingbird'?",
options: [
"Harper Lee",
"Mark Twain",
"J.K. Rowling",
"Ernest Hemingway"
],
correctAnswer: "Harper Lee"
},
{
questionId: 5,
questionText: "What is the largest planet in our solar system?",
options: [
"Earth",
"Mars",
"Jupiter",
"Saturn"
],
correctAnswer: "Jupiter"
},
{
questionId: 6,
questionText: "What is the speed of light?",
options: [
"300,000 km/s",
"150,000 km/s",
"100,000 km/s",
"50,000 km/s"
],
correctAnswer: "300,000 km/s"
},
{
questionId: 7,
questionText: "Who painted the Mona Lisa?",
options: [
"Vincent van Gogh",
"Pablo Picasso",
"Leonardo da Vinci",
"Claude Monet"
],
correctAnswer: "Leonardo da Vinci"
},
{
questionId: 8,
questionText: "What is the chemical symbol for gold?",
options: [
"Au",
"Ag",
"Pt",
"Pb"
],
correctAnswer: "Au"
},
{
questionId: 9,
questionText: "What is the tallest mountain in the world?",
options: [
"K2",
"Kangchenjunga",
"Mount Everest",
"Lhotse"
],
correctAnswer: "Mount Everest"
},
{
questionId: 10,
questionText: "What is the smallest unit of life?",
options: [
"Cell",
"Atom",
"Molecule",
"Organ"
],
correctAnswer: "Cell"
}
]
},
{
quizId: 2,
quizName: "Lorem Ipsum Dolor Sit",
attendQuestion: 45,
totalQuestion: 50,
internalMarks: 29,
attendance: 20,
questions: [
{
questionId: 1,
questionText: "What is the capital of Italy?",
options: [
"Rome",
"Venice",
"Florence",
"Milan"
],
correctAnswer: "Rome"
},
{
questionId: 2,
questionText: "What is 3 + 5?",
options: [
"7",
"8",
"9",
"10"
],
correctAnswer: "8"
},
{
questionId: 3,
questionText: "What is the freezing point of water?",
options: [
"0°C",
"32°C",
"100°C",
"273K"
],
correctAnswer: "0°C"
},
{
questionId: 4,
questionText: "Who wrote 'Pride and Prejudice'?",
options: [
"Jane Austen",
"Charles Dickens",
"Emily Brontë",
"George Eliot"
],
correctAnswer: "Jane Austen"
},
{
questionId: 5,
questionText: "What is the smallest planet in our solar system?",
options: [
"Mercury",
"Venus",
"Earth",
"Mars"
],
correctAnswer: "Mercury"
},
{
questionId: 6,
questionText: "What is the speed of sound?",
options: [
"343 m/s",
"300 m/s",
"1500 m/s",
"1000 m/s"
],
correctAnswer: "343 m/s"
},
{
questionId: 7,
questionText: "Who painted the Starry Night?",
options: [
"Vincent van Gogh",
"Pablo Picasso",
"Leonardo da Vinci",
"Claude Monet"
],
correctAnswer: "Vincent van Gogh"
},
{
questionId: 8,
questionText: "What is the chemical symbol for silver?",
options: [
"Au",
"Ag",
"Pt",
"Pb"
],
correctAnswer: "Ag"
},
{
questionId: 9,
questionText: "What is the second tallest mountain in the world?",
options: [
"K2",
"Kangchenjunga",
"Mount Everest",
"Lhotse"
],
correctAnswer: "K2"
},
{
questionId: 10,
questionText: "What is the largest organ in the human body?",
options: [
"Liver",
"Heart",
"Skin",
"Brain"
],
correctAnswer: "Skin"
}
]
}
]
},
{
moduleId: 2,
type: "Theory Quiz Scores",
moduleName: "Module 2",
attendQuestion: 42,
totalQuestion: 50,
internalMarks: 22,
attendance: 20,
quizzes: [
{
quizId: 1,
quizName: "Quiz 1",
attendQuestion: 49,
totalQuestion: 50,
internalMarks: 2,
attendance: 20,
questions: [
{
questionId: 1,
questionText: "What is the capital of Germany?",
options: [
"Berlin",
"Munich",
"Hamburg",
"Frankfurt"
],
correctAnswer: "Berlin"
},
{
questionId: 2,
questionText: "What is 5 + 3?",
options: [
"7",
"8",
"9",
"10"
],
correctAnswer: "8"
},
{
questionId: 3,
questionText: "What is the melting point of ice?",
options: [
"0°C",
"32°C",
"100°C",
"273K"
],
correctAnswer: "0°C"
},
{
questionId: 4,
questionText: "Who wrote '1984'?",
options: [
"George Orwell",
"Aldous Huxley",
"Ray Bradbury",
"J.D. Salinger"
],
correctAnswer: "George Orwell"
},
{
questionId: 5,
questionText: "What is the second smallest planet in our solar system?",
options: [
"Mercury",
"Venus",
"Earth",
"Mars"
],
correctAnswer: "Mars"
},
{
questionId: 6,
questionText: "What is the speed of light in a vacuum?",
options: [
"300,000 km/s",
"150,000 km/s",
"299,792 km/s",
"299,792 m/s"
],
correctAnswer: "299,792 km/s"
},
{
questionId: 7,
questionText: "Who painted the Last Supper?",
options: [
"Vincent van Gogh",
"Pablo Picasso",
"Leonardo da Vinci",
"Claude Monet"
],
correctAnswer: "Leonardo da Vinci"
},
{
questionId: 8,
questionText: "What is the chemical symbol for iron?",
options: [
"Fe",
"Ir",
"In",
"I"
],
correctAnswer: "Fe"
},
{
questionId: 9,
questionText: "What is the third tallest mountain in the world?",
options: [
"K2",
"Kangchenjunga",
"Mount Everest",
"Lhotse"
],
correctAnswer: "Kangchenjunga"
},
{
questionId: 10,
questionText: "What is the smallest bone in the human body?",
options: [
"Stapes",
"Femur",
"Tibia",
"Fibula"
],
correctAnswer: "Stapes"
}
]
}
]
}
]
}
res.json(quizModuleData);
};
module.exports = quizModuleData;

View File

@@ -0,0 +1,34 @@
const mysql = require("mysql2");
const quizModuleList = (req, res) => {
// res.send(req.query.doa); //get
// res.send(req.body.doa); //post
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if(err) {
console.error('Error connecting to the database:', err);
return;
}
console.log('Connected to the MariaDB database.');
});
const data = req.body;
const query = `SELECT * FROM quiz_modules WHERE moduleId = ?`;
const values = req.query.module_id;
connection.query(query, (err, results) => {
if (err) {
console.error('Error inserting data:', err);
res.status(500).send('Internal Server Error');
return;
}
res.status(200).json(results);
});
};
module.exports = quizModuleList;

View File

@@ -0,0 +1,34 @@
const mysql = require("mysql2");
const quizModuleList = (req, res) => {
// res.send(req.query.doa); //get
// res.send(req.body.doa); //post
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if(err) {
console.error('Error connecting to the database:', err);
return;
}
console.log('Connected to the MariaDB database.');
});
const data = req.body;
let values = req.query.module_id ? req.query.module_id : '';
const query = `SELECT * FROM quiz_modules WHERE moduleId = ?`;
// const values = req.query.module_id;
connection.query(query, values, (err, results) => {
if (err) {
console.error('Error inserting data:', err);
res.status(500).send('Internal Server Error');
return;
}
res.status(200).json(results);
});
};
module.exports = quizModuleList;

View File

@@ -0,0 +1,33 @@
const mysql = require("mysql2");
const quizNewModule = (req, res) => {
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if (err) {
console.error('Error connecting to the database:', err);
return;
}
console.log('Connected to the MariaDB database.');
});
const data = req.body;
const query = `INSERT INTO quiz_modules (moduleName, type) VALUES (?, ?)`;
const values = [data.moduleName, data.moduleType];
connection.query(query, values, (err, results) => {
if (err) {
console.error('Error inserting data:', err);
res.status(500).send('Internal Server Error');
return;
}
res.status(200).json(results);
});
}
module.exports = quizNewModule;

View File

@@ -0,0 +1,176 @@
const mysql = require("mysql2");
const quizzesScore = (req, res) => {
// res.send(req.query.doa); //get
// res.send(req.body.doa); //post
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if(err) {
console.error('Error connecting to the database:', err);
return;
}
console.log('Connected to the MariaDB database.');
});
const data = req.body;
const query = `SELECT * FROM quiz_score`;
connection.query(query, (err, results) => {
if (err) {
console.error('Error inserting data:', err);
res.status(500).send('Internal Server Error');
return;
}
res.status(200).json(results);
});
};
module.exports = quizzesScore;
// let quizData = [
// {
// quizId: 1,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 1",
// percentage: "60"
// },
// {
// quizId: 2,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 2",
// percentage: "75"
// },
// {
// quizId: 3,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 3",
// percentage: "80"
// },
// {
// quizId: 4,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 4",
// percentage: "65"
// },
// {
// quizId: 5,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 5",
// percentage: "70"
// },
// {
// quizId: 6,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 6",
// percentage: "85"
// },
// {
// quizId: 7,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 7",
// percentage: "90"
// },
// {
// quizId: 8,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 8",
// percentage: "95"
// },
// {
// quizId: 9,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 9",
// percentage: "88"
// },
// {
// quizId: 10,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 10",
// percentage: "92"
// },
// {
// quizId: 11,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 11",
// percentage: "77"
// },
// {
// quizId: 12,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 12",
// percentage: "82"
// },
// {
// quizId: 13,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 13",
// percentage: "68"
// },
// {
// quizId: 14,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 14",
// percentage: "73"
// },
// {
// quizId: 15,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 15",
// percentage: "79"
// },
// {
// quizId: 16,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 16",
// percentage: "87"
// },
// {
// quizId: 17,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 17",
// percentage: "93"
// },
// {
// quizId: 18,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 18",
// percentage: "67"
// },
// {
// quizId: 19,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 19",
// percentage: "89"
// },
// {
// quizId: 20,
// quizType: "AI Quiz",
// quizName: "Assessment on Special Education - 20",
// percentage: "91"
// }
// ]
// res.json(quizData);

View File

@@ -0,0 +1,93 @@
const mysql = require("mysql2");
const resultAfterQuizSubmit = (req, res) => {
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if (err) {
console.error('Error connecting to the database:', err);
res.status(500).send('Internal Server Error');
return;
}
console.log('Connected to the MariaDB database.');
});
const queryData = req.query;
const responseValues = [queryData.id]; // Ensure this is an array
const responseQuery = `SELECT * FROM quiz_response WHERE quizId = ?`;
connection.query(responseQuery, responseValues, (err, results) => {
if (err) {
console.error('Error retrieving data:', err);
res.status(500).send('Internal Server Error');
connection.end();
return;
}
let questionsProcessed = 0;
let allResults = [];
results.forEach((resultData, index) => {
const questionId = [resultData.questionId]; // Ensure this is an array
const answerQuery = `SELECT * FROM quiz_questions WHERE questionId = ?`;
connection.query(answerQuery, questionId, (err, questionResults) => {
if (err) {
console.error('Error retrieving question data:', err);
res.status(500).send('Internal Server Error');
connection.end();
return;
}
allResults.push({
response: resultData,
question: questionResults[0] // Assuming it returns one result per questionId
});
questionsProcessed++;
if (questionsProcessed === results.length) {
let correctAnswers = 0;
allResults.forEach(item => {
if (item.response.selectedOption === item.question.correctAnswer) {
correctAnswers++;
}
});
let quizMessage;
if(correctAnswers > 3){
quizMessage = 'Congratulations on your achievement.'
}else if(correctAnswers < 3){
quizMessage = 'You not paassed the quiz Try Again'
}
res.status(200).json({
totalQuestions: results.length,
correctAnswers: correctAnswers,
message: quizMessage,
details: allResults
});
connection.end();
}
});
});
if (results.length === 0) {
res.status(200).json({
totalQuestions: 0,
correctAnswers: 0,
details: []
});
connection.end();
}
});
};
module.exports = resultAfterQuizSubmit;

View File

@@ -0,0 +1,66 @@
var MongoClient = require('mongodb').MongoClient;
const AWS = require('aws-sdk');
const saveGameScore = (req, res) => {
const url = process.env.MONGODB_URL;
const dbName = process.env.MONGO_DB_NAME;
const client = new MongoClient(url, { useUnifiedTopology: true });
client.connect((err) => {
if (err) {
console.error('Failed to connect to the server', err);
return;
}
// console.log('Connected successfully to server');
const db = client.db(dbName);
const collection = db.collection('gameData');
// const data = req.body;
const { userId, gameName, gameID, gameTime, score, screenShot } = req.body;
const data = {
userId: userId,
gameName: gameName,
gameID: gameID
};
collection.insertOne(data, (err, result) => {
if (err) {
console.error('Failed to insert document', err);
} else {
// console.log('Document inserted with _id: ', result.insertedId);
}
client.close((err) => {
if (err) {
console.error('Failed to close connection', err);
} else {
// console.log('Connection closed');
const s3 = new AWS.S3({
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
region: process.env.AWS_REGION
});
if (screenShot != undefined) {
// Upload image to S3
let base64Image = screenShot.split(";base64,").pop();
const buffer = Buffer.from(base64Image, 'base64');
const s3Params = {
Bucket: process.env.S3_BUCKET_NAME,
Key: `images/${result.insertedId}.png`, // Change the file extension to .png
Body: buffer,
ContentEncoding: 'base64',
ContentType: 'image/jpeg' // Change the content type to image/png
};
try {
const data = s3.upload(s3Params).promise();
console.log(`File uploaded successfully at ${data.Location}`);
} catch (err) {
console.error(err);
}
};
res.send(result.insertedId);
}
});
});
});
};
module.exports = saveGameScore;

View File

@@ -0,0 +1,33 @@
const mysql = require("mysql2");
const savePostData = (req, res) => {
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if (err) {
console.error('Error connecting to the database:', err);
return;
}
console.log('Connected to the MariaDB database.');
});
const data = req.body;
const query = `INSERT INTO tst (name, email) VALUES (?, ?)`;
const values = [data.name, data.email];
connection.query(query, values, (err, results) => {
if (err) {
console.error('Error inserting data:', err);
res.status(500).send('Internal Server Error');
return;
}
res.status(200).send('Data inserted successfully');
});
}
module.exports = savePostData;

View File

@@ -0,0 +1,59 @@
const mysql = require("mysql2");
const saveQuizResponse = (req, res) => {
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if (err) {
console.error('Error connecting to the database:', err);
res.status(500).send('Internal Server Error');
return;
}
console.log('Connected to the MariaDB database.');
});
const data = req.body;
// console.log(data);
const query = `INSERT INTO quiz_response (questionId, quizId, selectedOption) VALUES (?, ?, ?)`;
let errorOccurred = false;
let promises = [];
data.forEach((response) => {
const values = [response.questionId, response.quizId, response.selectedOption];
const promise = new Promise((resolve, reject) => {
connection.query(query, values, (err, results) => {
if (err) {
console.error('Error inserting data:', err);
errorOccurred = true;
reject(err);
} else {
resolve(results);
}
});
});
promises.push(promise);
});
Promise.all(promises)
.then(() => {
if (!errorOccurred) {
res.status(200).json({success: true, quizId: data[0].quizId, message: 'All responses saved successfully.' });
}
})
.catch((error) => {
console.error('Error saving responses:', error);
res.status(500).send('Internal Server Error');
})
.finally(() => {
connection.end();
});
};
module.exports = saveQuizResponse;

View File

@@ -0,0 +1,231 @@
const mysql = require("mysql2");
const topPerformers = (req, res) => {
// res.send(req.query.doa); //get
// res.send(req.body.doa); //post
const connection = mysql.createConnection({
host: process.env.MARIA_HOST,
user: process.env.MARIA_USER,
password: process.env.MARIA_PASS,
database: process.env.MARIA_DBNM
});
connection.connect((err) => {
if(err) {
console.error('Error connecting to the database:', err);
return;
}
console.log('Connected to the MariaDB database.');
});
const data = req.body;
const query = `SELECT * FROM top_performers`;
connection.query(query, (err, results) => {
if (err) {
console.error('Error inserting data:', err);
res.status(500).send('Internal Server Error');
return;
}
res.status(200).json(results);
});
};
module.exports = topPerformers;
// let performersData = [
// {
// id: "1",
// name: "Eiden",
// score: "48/50",
// points: "999",
// rank: "1",
// program: "Graduate Program",
// avatar: "/assets/avatar1.png"
// },
// {
// id: "2",
// name: "Jackson",
// score: "45/50",
// points: "997",
// rank: "2",
// program: "Graduate Program",
// avatar: "/assets/avatar2.png"
// },
// {
// id: "3",
// name: "Emma Aria",
// score: "43/50",
// points: "994",
// rank: "3",
// program: "Graduate Program",
// avatar: "/assets/avatar3.png"
// },
// {
// id: "4",
// name: "John Doe",
// score: "40/50",
// points: "990",
// rank: "4",
// program: "Graduate Program",
// avatar: "/assets/avatar4.png"
// },
// {
// id: "5",
// name: "Jane Cooper",
// score: "37/50",
// points: "987",
// rank: "5",
// program: "Graduate Program",
// avatar: "/assets/avatar5.png"
// },
// {
// id: "6",
// name: "John Doe",
// score: "35/50",
// points: "982",
// rank: "6",
// program: "Graduate Program",
// avatar: "/assets/avatar6.png"
// },
// {
// id: "7",
// name: "Alice",
// score: "33/50",
// points: "980",
// rank: "7",
// program: "Graduate Program",
// avatar: "/assets/avatar1.png"
// },
// {
// id: "8",
// name: "Bob",
// score: "32/50",
// points: "978",
// rank: "8",
// program: "Graduate Program",
// avatar: "/assets/avatar2.png"
// },
// {
// id: "9",
// name: "Charlie",
// score: "30/50",
// points: "975",
// rank: "9",
// program: "Graduate Program",
// avatar: "/assets/avatar3.png"
// },
// {
// id: "10",
// name: "Diana",
// score: "28/50",
// points: "972",
// rank: "10",
// program: "Graduate Program",
// avatar: "/assets/avatar4.png"
// },
// {
// id: "11",
// name: "Edward",
// score: "27/50",
// points: "970",
// rank: "11",
// program: "Graduate Program",
// avatar: "/assets/avatar5.png"
// },
// {
// id: "12",
// name: "Fiona",
// score: "26/50",
// points: "968",
// rank: "12",
// program: "Graduate Program",
// avatar: "/assets/avatar6.png"
// },
// {
// id: "13",
// name: "George",
// score: "25/50",
// points: "965",
// rank: "13",
// program: "Graduate Program",
// avatar: "/assets/avatar1.png"
// },
// {
// id: "14",
// name: "Hannah",
// score: "23/50",
// points: "962",
// rank: "14",
// program: "Graduate Program",
// avatar: "/assets/avatar2.png"
// },
// {
// id: "15",
// name: "Ian",
// score: "22/50",
// points: "960",
// rank: "15",
// program: "Graduate Program",
// avatar: "/assets/avatar3.png"
// },
// {
// id: "16",
// name: "Julia",
// score: "20/50",
// points: "957",
// rank: "16",
// program: "Graduate Program",
// avatar: "/assets/avatar4.png"
// },
// {
// id: "17",
// name: "Kyle",
// score: "19/50",
// points: "955",
// rank: "17",
// program: "Graduate Program",
// avatar: "/assets/avatar5.png"
// },
// {
// id: "18",
// name: "Laura",
// score: "18/50",
// points: "953",
// rank: "18",
// program: "Graduate Program",
// avatar: "/assets/avatar6.png"
// },
// {
// id: "19",
// name: "Michael",
// score: "17/50",
// points: "950",
// rank: "19",
// program: "Graduate Program",
// avatar: "/assets/avatar1.png"
// },
// {
// id: "20",
// name: "Nancy",
// score: "16/50",
// points: "947",
// rank: "20",
// program: "Graduate Program",
// avatar: "/assets/avatar2.png"
// },
// {
// id: "21",
// name: "Oliver",
// score: "15/50",
// points: "945",
// rank: "21",
// program: "Graduate Program",
// avatar: "/assets/avatar3.png"
// }
// ];
// res.json(performersData);

378
src/routes/v1/api.route.js Normal file
View File

@@ -0,0 +1,378 @@
// const express = require('express');
const express = require("express");
const Ping = require("../api/apiTest");
const apiTest = require("../api/apiTest");
const topPerformers = require("../api/topPerformers");
const classMates = require("../api/classMates");
const continueLearning = require("../api/continueLearning");
const knowledgeQuests = require("../api/knowledgeQuests");
const quizzesScore = require("../api/quizzesScore");
const quizModuleData = require("../api/quizModuleData");
const knowledgeQuestsAllContent = require("../api/knowledgeQuestsAllContent");
const knowledgeQuestsCompleted = require("../api/knowledgeQuestsCompleted");
const quizModuleList = require("../api/quizModuleList");
const quizNewModule = require("../api/quizNewModule");
const newQuiz = require("../api/newQuiz");
const quizList = require("../api/quizList");
const savePostData = require("../api/savePostData");
// const signIn = require("../api/signIn");
const questionList = require("../api/questionList");
const newQuestion = require("../api/newQuestion");
const saveQuizResponse = require("../api/saveQuizResponse");
const getGameScore = require("../api/getGameScore");
const resultAfterQuizSubmit = require("../api/resultAfterQuizSubmit");
const generateQuestions = require("../api/generateQuestions");
const saveGameScore = require("../api/saveGameScore");
const router = express.Router();
/* GET home page. */
router.get("/ping", (req, res) => {
Ping(req, res);
});
/* GET home page. */
router.get("/apiTest", (req, res) => {
apiTest(req, res);
});
// Classmates Directory page top performers section data
router.get("/top-performers", (req, res) => {
topPerformers(req, res);
});
// Classmates Directory page top class mates section data
router.get("/class-mates", (req, res) => {
classMates(req, res);
});
// Student Dashboard page Continue Learning section data
router.get("/continue-learning", (req, res) => {
continueLearning(req, res);
});
// Student Dashboard page knowledge-Quests section data
router.get("/knowledge-quests", (req, res) => {
knowledgeQuests(req, res);
});
// Progress Review page quiz details section data
router.get("/quiz-score", (req, res) => {
quizzesScore(req, res);
});
// Progress Review page quiz module section data
router.get("/quiz-module", (req, res) => {
quizModuleData(req, res);
});
// knowledge Quests page All Content section data
router.get("/all-assesment", (req, res) => {
knowledgeQuestsAllContent(req, res);
});
// knowledge Quests page Completed section data
router.get("/complete-assesment", (req, res) => {
knowledgeQuestsCompleted(req, res);
});
/* GET home page. */
router.post("/savePostData", (req, res) => {
savePostData(req, res);
});
// For Sign in
// router.post("/signin", (req, res) => {
// signIn(req, res);
// });
// For Quiz Module list Data
router.get("/quiz-module-list", (req, res) => {
quizModuleList(req, res);
});
// For Create new module
router.post("/create-module", (req, res) => {
quizNewModule(req, res);
});
// For Create new Quiz
router.post("/create-quiz", (req, res) => {
newQuiz(req, res);
});
// For Quiz List data
router.get("/quiz-list", (req, res) => {
quizList(req, res);
});
// For Quiz Question List data
router.get("/question-list", (req, res) => {
questionList(req, res);
});
// For Quiz Question List data
router.post("/create-question", (req, res) => {
newQuestion(req, res);
});
// For Quiz Question List data
router.post("/save-quiz-response", (req, res) => {
saveQuizResponse(req, res);
});
// For Quiz Question List data
router.post("/getGameScore", (req, res) => {
getGameScore(req, res);
});
// For Quiz Result After Submit Quiz
router.get("/quizresult-aftersubmit", (req, res) => {
resultAfterQuizSubmit(req, res);
});
// For Quiz Result After Submit Quiz
router.post("/generateQuestions", (req, res) => {
generateQuestions(req, res);
});
// For Quiz Result After Submit Quiz
router.post("/saveGameScore", (req, res) => {
saveGameScore(req, res);
});
module.exports = router;
/**
* @swagger
* tags:
* name: Users
* description: User management and retrieval
*/
/**
* @swagger
* /users:
* post:
* summary: Create a user
* description: Only admins can create other users.
* tags: [Users]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - email
* - password
* - role
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* role:
* type: string
* enum: [user, admin]
* example:
* name: fake name
* email: fake@example.com
* password: password1
* role: user
* responses:
* "201":
* description: Created
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
*
* get:
* summary: Get all users
* description: Only admins can retrieve all users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: name
* schema:
* type: string
* description: User name
* - in: query
* name: role
* schema:
* type: string
* description: User role
* - in: query
* name: sortBy
* schema:
* type: string
* description: sort by query in the form of field:desc/asc (ex. name:asc)
* - in: query
* name: limit
* schema:
* type: integer
* minimum: 1
* default: 10
* description: Maximum number of users
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* default: 1
* description: Page number
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* results:
* type: array
* items:
* $ref: '#/components/schemas/User'
* page:
* type: integer
* example: 1
* limit:
* type: integer
* example: 10
* totalPages:
* type: integer
* example: 1
* totalResults:
* type: integer
* example: 1
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
*/
/**
* @swagger
* /users/{id}:
* get:
* summary: Get a user
* description: Logged in users can fetch only their own user information. Only admins can fetch other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*
* patch:
* summary: Update a user
* description: Logged in users can only update their own information. Only admins can update other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* example:
* name: fake name
* email: fake@example.com
* password: password1
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*
* delete:
* summary: Delete a user
* description: Logged in users can delete only themselves. Only admins can delete other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* responses:
* "200":
* description: No content
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*/

291
src/routes/v1/auth.route.js Normal file
View File

@@ -0,0 +1,291 @@
const express = require('express');
const validate = require('../../middlewares/validate');
const authValidation = require('../../validations/auth.validation');
const authController = require('../../controllers/auth.controller');
const auth = require('../../middlewares/auth');
const router = express.Router();
router.post('/register', validate(authValidation.register), authController.register);
router.post('/login', validate(authValidation.login), authController.login);
router.post('/logout', validate(authValidation.logout), authController.logout);
router.post('/refresh-tokens', validate(authValidation.refreshTokens), authController.refreshTokens);
router.post('/forgot-password', validate(authValidation.forgotPassword), authController.forgotPassword);
router.post('/reset-password', validate(authValidation.resetPassword), authController.resetPassword);
router.post('/send-verification-email', auth(), authController.sendVerificationEmail);
router.post('/verify-email', validate(authValidation.verifyEmail), authController.verifyEmail);
module.exports = router;
/**
* @swagger
* tags:
* name: Auth
* description: Authentication
*/
/**
* @swagger
* /auth/register:
* post:
* summary: Register as user
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - email
* - password
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* example:
* name: fake name
* email: fake@example.com
* password: password1
* responses:
* "201":
* description: Created
* content:
* application/json:
* schema:
* type: object
* properties:
* user:
* $ref: '#/components/schemas/User'
* tokens:
* $ref: '#/components/schemas/AuthTokens'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
*/
/**
* @swagger
* /auth/login:
* post:
* summary: Login
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - email
* - password
* properties:
* email:
* type: string
* format: email
* password:
* type: string
* format: password
* example:
* email: fake@example.com
* password: password1
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* user:
* $ref: '#/components/schemas/User'
* tokens:
* $ref: '#/components/schemas/AuthTokens'
* "401":
* description: Invalid email or password
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* example:
* code: 401
* message: Invalid email or password
*/
/**
* @swagger
* /auth/logout:
* post:
* summary: Logout
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - refreshToken
* properties:
* refreshToken:
* type: string
* example:
* refreshToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg
* responses:
* "204":
* description: No content
* "404":
* $ref: '#/components/responses/NotFound'
*/
/**
* @swagger
* /auth/refresh-tokens:
* post:
* summary: Refresh auth tokens
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - refreshToken
* properties:
* refreshToken:
* type: string
* example:
* refreshToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/AuthTokens'
* "401":
* $ref: '#/components/responses/Unauthorized'
*/
/**
* @swagger
* /auth/forgot-password:
* post:
* summary: Forgot password
* description: An email will be sent to reset password.
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - email
* properties:
* email:
* type: string
* format: email
* example:
* email: fake@example.com
* responses:
* "204":
* description: No content
* "404":
* $ref: '#/components/responses/NotFound'
*/
/**
* @swagger
* /auth/reset-password:
* post:
* summary: Reset password
* tags: [Auth]
* parameters:
* - in: query
* name: token
* required: true
* schema:
* type: string
* description: The reset password token
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - password
* properties:
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* example:
* password: password1
* responses:
* "204":
* description: No content
* "401":
* description: Password reset failed
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* example:
* code: 401
* message: Password reset failed
*/
/**
* @swagger
* /auth/send-verification-email:
* post:
* summary: Send verification email
* description: An email will be sent to verify email.
* tags: [Auth]
* security:
* - bearerAuth: []
* responses:
* "204":
* description: No content
* "401":
* $ref: '#/components/responses/Unauthorized'
*/
/**
* @swagger
* /auth/verify-email:
* post:
* summary: verify email
* tags: [Auth]
* parameters:
* - in: query
* name: token
* required: true
* schema:
* type: string
* description: The verify email token
* responses:
* "204":
* description: No content
* "401":
* description: verify email failed
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* example:
* code: 401
* message: verify email failed
*/

View File

@@ -0,0 +1,21 @@
const express = require('express');
const swaggerJsdoc = require('swagger-jsdoc');
const swaggerUi = require('swagger-ui-express');
const swaggerDefinition = require('../../docs/swaggerDef');
const router = express.Router();
const specs = swaggerJsdoc({
swaggerDefinition,
apis: ['src/docs/*.yml', 'src/routes/v1/*.js'],
});
router.use('/', swaggerUi.serve);
router.get(
'/',
swaggerUi.setup(specs, {
explorer: true,
})
);
module.exports = router;

44
src/routes/v1/index.js Normal file
View File

@@ -0,0 +1,44 @@
const express = require('express');
const authRoute = require('./auth.route');
const userRoute = require('./user.route');
const apiRoute = require('./api.route');
const docsRoute = require('./docs.route');
const config = require('../../config/config');
const router = express.Router();
const defaultRoutes = [
{
path: '/auth',
route: authRoute,
},
{
path: '/users',
route: userRoute,
},
{
path: '/api',
route: apiRoute,
},
];
const devRoutes = [
// routes available only in development mode
{
path: '/docs',
route: docsRoute,
},
];
defaultRoutes.forEach((route) => {
router.use(route.path, route.route);
});
/* istanbul ignore next */
if (config.env === 'development') {
devRoutes.forEach((route) => {
router.use(route.path, route.route);
});
}
module.exports = router;

252
src/routes/v1/user.route.js Normal file
View File

@@ -0,0 +1,252 @@
const express = require('express');
const auth = require('../../middlewares/auth');
const validate = require('../../middlewares/validate');
const userValidation = require('../../validations/user.validation');
const userController = require('../../controllers/user.controller');
const router = express.Router();
router
.route('/')
.post(auth('manageUsers'), validate(userValidation.createUser), userController.createUser)
.get(auth('getUsers'), validate(userValidation.getUsers), userController.getUsers);
router
.route('/:userId')
.get(auth('getUsers'), validate(userValidation.getUser), userController.getUser)
.patch(auth('manageUsers'), validate(userValidation.updateUser), userController.updateUser)
.delete(auth('manageUsers'), validate(userValidation.deleteUser), userController.deleteUser);
module.exports = router;
/**
* @swagger
* tags:
* name: Users
* description: User management and retrieval
*/
/**
* @swagger
* /users:
* post:
* summary: Create a user
* description: Only admins can create other users.
* tags: [Users]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - email
* - password
* - role
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* role:
* type: string
* enum: [user, admin]
* example:
* name: fake name
* email: fake@example.com
* password: password1
* role: user
* responses:
* "201":
* description: Created
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
*
* get:
* summary: Get all users
* description: Only admins can retrieve all users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: name
* schema:
* type: string
* description: User name
* - in: query
* name: role
* schema:
* type: string
* description: User role
* - in: query
* name: sortBy
* schema:
* type: string
* description: sort by query in the form of field:desc/asc (ex. name:asc)
* - in: query
* name: limit
* schema:
* type: integer
* minimum: 1
* default: 10
* description: Maximum number of users
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* default: 1
* description: Page number
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* results:
* type: array
* items:
* $ref: '#/components/schemas/User'
* page:
* type: integer
* example: 1
* limit:
* type: integer
* example: 10
* totalPages:
* type: integer
* example: 1
* totalResults:
* type: integer
* example: 1
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
*/
/**
* @swagger
* /users/{id}:
* get:
* summary: Get a user
* description: Logged in users can fetch only their own user information. Only admins can fetch other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*
* patch:
* summary: Update a user
* description: Logged in users can only update their own information. Only admins can update other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* example:
* name: fake name
* email: fake@example.com
* password: password1
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*
* delete:
* summary: Delete a user
* description: Logged in users can delete only themselves. Only admins can delete other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* responses:
* "200":
* description: No content
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*/

View File

@@ -0,0 +1,99 @@
const httpStatus = require('http-status');
const tokenService = require('./token.service');
const userService = require('./user.service');
const Token = require('../models/token.model');
const ApiError = require('../utils/ApiError');
const { tokenTypes } = require('../config/tokens');
/**
* Login with username and password
* @param {string} email
* @param {string} password
* @returns {Promise<User>}
*/
const loginUserWithEmailAndPassword = async (email, password) => {
const user = await userService.getUserByEmail(email);
if (!user || !(await user.isPasswordMatch(password))) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Incorrect email or password');
}
return user;
};
/**
* Logout
* @param {string} refreshToken
* @returns {Promise}
*/
const logout = async (refreshToken) => {
const refreshTokenDoc = await Token.findOne({ token: refreshToken, type: tokenTypes.REFRESH, blacklisted: false });
if (!refreshTokenDoc) {
throw new ApiError(httpStatus.NOT_FOUND, 'Not found');
}
await refreshTokenDoc.remove();
};
/**
* Refresh auth tokens
* @param {string} refreshToken
* @returns {Promise<Object>}
*/
const refreshAuth = async (refreshToken) => {
try {
const refreshTokenDoc = await tokenService.verifyToken(refreshToken, tokenTypes.REFRESH);
const user = await userService.getUserById(refreshTokenDoc.user);
if (!user) {
throw new Error();
}
await refreshTokenDoc.remove();
return tokenService.generateAuthTokens(user);
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
}
};
/**
* Reset password
* @param {string} resetPasswordToken
* @param {string} newPassword
* @returns {Promise}
*/
const resetPassword = async (resetPasswordToken, newPassword) => {
try {
const resetPasswordTokenDoc = await tokenService.verifyToken(resetPasswordToken, tokenTypes.RESET_PASSWORD);
const user = await userService.getUserById(resetPasswordTokenDoc.user);
if (!user) {
throw new Error();
}
await userService.updateUserById(user.id, { password: newPassword });
await Token.deleteMany({ user: user.id, type: tokenTypes.RESET_PASSWORD });
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Password reset failed');
}
};
/**
* Verify email
* @param {string} verifyEmailToken
* @returns {Promise}
*/
const verifyEmail = async (verifyEmailToken) => {
try {
const verifyEmailTokenDoc = await tokenService.verifyToken(verifyEmailToken, tokenTypes.VERIFY_EMAIL);
const user = await userService.getUserById(verifyEmailTokenDoc.user);
if (!user) {
throw new Error();
}
await Token.deleteMany({ user: user.id, type: tokenTypes.VERIFY_EMAIL });
await userService.updateUserById(user.id, { isEmailVerified: true });
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Email verification failed');
}
};
module.exports = {
loginUserWithEmailAndPassword,
logout,
refreshAuth,
resetPassword,
verifyEmail,
};

View File

@@ -0,0 +1,63 @@
const nodemailer = require('nodemailer');
const config = require('../config/config');
const logger = require('../config/logger');
const transport = nodemailer.createTransport(config.email.smtp);
/* istanbul ignore next */
if (config.env !== 'test') {
transport
.verify()
.then(() => logger.info('Connected to email server'))
.catch(() => logger.warn('Unable to connect to email server. Make sure you have configured the SMTP options in .env'));
}
/**
* Send an email
* @param {string} to
* @param {string} subject
* @param {string} text
* @returns {Promise}
*/
const sendEmail = async (to, subject, text) => {
const msg = { from: config.email.from, to, subject, text };
await transport.sendMail(msg);
};
/**
* Send reset password email
* @param {string} to
* @param {string} token
* @returns {Promise}
*/
const sendResetPasswordEmail = async (to, token) => {
const subject = 'Reset password';
// replace this url with the link to the reset password page of your front-end app
const resetPasswordUrl = `http://link-to-app/reset-password?token=${token}`;
const text = `Dear user,
To reset your password, click on this link: ${resetPasswordUrl}
If you did not request any password resets, then ignore this email.`;
await sendEmail(to, subject, text);
};
/**
* Send verification email
* @param {string} to
* @param {string} token
* @returns {Promise}
*/
const sendVerificationEmail = async (to, token) => {
const subject = 'Email Verification';
// replace this url with the link to the email verification page of your front-end app
const verificationEmailUrl = `http://link-to-app/verify-email?token=${token}`;
const text = `Dear user,
To verify your email, click on this link: ${verificationEmailUrl}
If you did not create an account, then ignore this email.`;
await sendEmail(to, subject, text);
};
module.exports = {
transport,
sendEmail,
sendResetPasswordEmail,
sendVerificationEmail,
};

4
src/services/index.js Normal file
View File

@@ -0,0 +1,4 @@
module.exports.authService = require('./auth.service');
module.exports.emailService = require('./email.service');
module.exports.tokenService = require('./token.service');
module.exports.userService = require('./user.service');

View File

@@ -0,0 +1,123 @@
const jwt = require('jsonwebtoken');
const moment = require('moment');
const httpStatus = require('http-status');
const config = require('../config/config');
const userService = require('./user.service');
const { Token } = require('../models');
const ApiError = require('../utils/ApiError');
const { tokenTypes } = require('../config/tokens');
/**
* Generate token
* @param {ObjectId} userId
* @param {Moment} expires
* @param {string} type
* @param {string} [secret]
* @returns {string}
*/
const generateToken = (userId, expires, type, secret = config.jwt.secret) => {
const payload = {
sub: userId,
iat: moment().unix(),
exp: expires.unix(),
type,
};
return jwt.sign(payload, secret);
};
/**
* Save a token
* @param {string} token
* @param {ObjectId} userId
* @param {Moment} expires
* @param {string} type
* @param {boolean} [blacklisted]
* @returns {Promise<Token>}
*/
const saveToken = async (token, userId, expires, type, blacklisted = false) => {
const tokenDoc = await Token.create({
token,
user: userId,
expires: expires.toDate(),
type,
blacklisted,
});
return tokenDoc;
};
/**
* Verify token and return token doc (or throw an error if it is not valid)
* @param {string} token
* @param {string} type
* @returns {Promise<Token>}
*/
const verifyToken = async (token, type) => {
const payload = jwt.verify(token, config.jwt.secret);
const tokenDoc = await Token.findOne({ token, type, user: payload.sub, blacklisted: false });
if (!tokenDoc) {
throw new Error('Token not found');
}
return tokenDoc;
};
/**
* Generate auth tokens
* @param {User} user
* @returns {Promise<Object>}
*/
const generateAuthTokens = async (user) => {
const accessTokenExpires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const accessToken = generateToken(user.id, accessTokenExpires, tokenTypes.ACCESS);
const refreshTokenExpires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = generateToken(user.id, refreshTokenExpires, tokenTypes.REFRESH);
await saveToken(refreshToken, user.id, refreshTokenExpires, tokenTypes.REFRESH);
return {
access: {
token: accessToken,
expires: accessTokenExpires.toDate(),
},
refresh: {
token: refreshToken,
expires: refreshTokenExpires.toDate(),
},
};
};
/**
* Generate reset password token
* @param {string} email
* @returns {Promise<string>}
*/
const generateResetPasswordToken = async (email) => {
const user = await userService.getUserByEmail(email);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email');
}
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = generateToken(user.id, expires, tokenTypes.RESET_PASSWORD);
await saveToken(resetPasswordToken, user.id, expires, tokenTypes.RESET_PASSWORD);
return resetPasswordToken;
};
/**
* Generate verify email token
* @param {User} user
* @returns {Promise<string>}
*/
const generateVerifyEmailToken = async (user) => {
const expires = moment().add(config.jwt.verifyEmailExpirationMinutes, 'minutes');
const verifyEmailToken = generateToken(user.id, expires, tokenTypes.VERIFY_EMAIL);
await saveToken(verifyEmailToken, user.id, expires, tokenTypes.VERIFY_EMAIL);
return verifyEmailToken;
};
module.exports = {
generateToken,
saveToken,
verifyToken,
generateAuthTokens,
generateResetPasswordToken,
generateVerifyEmailToken,
};

View File

@@ -0,0 +1,89 @@
const httpStatus = require('http-status');
const { User } = require('../models');
const ApiError = require('../utils/ApiError');
/**
* Create a user
* @param {Object} userBody
* @returns {Promise<User>}
*/
const createUser = async (userBody) => {
if (await User.isEmailTaken(userBody.email)) {
throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
}
return User.create(userBody);
};
/**
* Query for users
* @param {Object} filter - Mongo filter
* @param {Object} options - Query options
* @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc)
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
* @param {number} [options.page] - Current page (default = 1)
* @returns {Promise<QueryResult>}
*/
const queryUsers = async (filter, options) => {
const users = await User.paginate(filter, options);
return users;
};
/**
* Get user by id
* @param {ObjectId} id
* @returns {Promise<User>}
*/
const getUserById = async (id) => {
return User.findById(id);
};
/**
* Get user by email
* @param {string} email
* @returns {Promise<User>}
*/
const getUserByEmail = async (email) => {
return User.findOne({ email });
};
/**
* Update user by id
* @param {ObjectId} userId
* @param {Object} updateBody
* @returns {Promise<User>}
*/
const updateUserById = async (userId, updateBody) => {
const user = await getUserById(userId);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
}
if (updateBody.email && (await User.isEmailTaken(updateBody.email, userId))) {
throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
}
Object.assign(user, updateBody);
await user.save();
return user;
};
/**
* Delete user by id
* @param {ObjectId} userId
* @returns {Promise<User>}
*/
const deleteUserById = async (userId) => {
const user = await getUserById(userId);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
}
await user.remove();
return user;
};
module.exports = {
createUser,
queryUsers,
getUserById,
getUserByEmail,
updateUserById,
deleteUserById,
};

14
src/utils/ApiError.js Normal file
View File

@@ -0,0 +1,14 @@
class ApiError extends Error {
constructor(statusCode, message, isOperational = true, stack = '') {
super(message);
this.statusCode = statusCode;
this.isOperational = isOperational;
if (stack) {
this.stack = stack;
} else {
Error.captureStackTrace(this, this.constructor);
}
}
}
module.exports = ApiError;

5
src/utils/catchAsync.js Normal file
View File

@@ -0,0 +1,5 @@
const catchAsync = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch((err) => next(err));
};
module.exports = catchAsync;

17
src/utils/pick.js Normal file
View File

@@ -0,0 +1,17 @@
/**
* Create an object composed of the picked object properties
* @param {Object} object
* @param {string[]} keys
* @returns {Object}
*/
const pick = (object, keys) => {
return keys.reduce((obj, key) => {
if (object && Object.prototype.hasOwnProperty.call(object, key)) {
// eslint-disable-next-line no-param-reassign
obj[key] = object[key];
}
return obj;
}, {});
};
module.exports = pick;

View File

@@ -0,0 +1,60 @@
const Joi = require('joi');
const { password } = require('./custom.validation');
const register = {
body: Joi.object().keys({
email: Joi.string().required().email(),
password: Joi.string().required().custom(password),
name: Joi.string().required(),
}),
};
const login = {
body: Joi.object().keys({
email: Joi.string().required(),
password: Joi.string().required(),
}),
};
const logout = {
body: Joi.object().keys({
refreshToken: Joi.string().required(),
}),
};
const refreshTokens = {
body: Joi.object().keys({
refreshToken: Joi.string().required(),
}),
};
const forgotPassword = {
body: Joi.object().keys({
email: Joi.string().email().required(),
}),
};
const resetPassword = {
query: Joi.object().keys({
token: Joi.string().required(),
}),
body: Joi.object().keys({
password: Joi.string().required().custom(password),
}),
};
const verifyEmail = {
query: Joi.object().keys({
token: Joi.string().required(),
}),
};
module.exports = {
register,
login,
logout,
refreshTokens,
forgotPassword,
resetPassword,
verifyEmail,
};

View File

@@ -0,0 +1,21 @@
const objectId = (value, helpers) => {
if (!value.match(/^[0-9a-fA-F]{24}$/)) {
return helpers.message('"{{#label}}" must be a valid mongo id');
}
return value;
};
const password = (value, helpers) => {
if (value.length < 8) {
return helpers.message('password must be at least 8 characters');
}
if (!value.match(/\d/) || !value.match(/[a-zA-Z]/)) {
return helpers.message('password must contain at least 1 letter and 1 number');
}
return value;
};
module.exports = {
objectId,
password,
};

2
src/validations/index.js Normal file
View File

@@ -0,0 +1,2 @@
module.exports.authValidation = require('./auth.validation');
module.exports.userValidation = require('./user.validation');

View File

@@ -0,0 +1,54 @@
const Joi = require('joi');
const { password, objectId } = require('./custom.validation');
const createUser = {
body: Joi.object().keys({
email: Joi.string().required().email(),
password: Joi.string().required().custom(password),
name: Joi.string().required(),
role: Joi.string().required().valid('user', 'admin'),
}),
};
const getUsers = {
query: Joi.object().keys({
name: Joi.string(),
role: Joi.string(),
sortBy: Joi.string(),
limit: Joi.number().integer(),
page: Joi.number().integer(),
}),
};
const getUser = {
params: Joi.object().keys({
userId: Joi.string().custom(objectId),
}),
};
const updateUser = {
params: Joi.object().keys({
userId: Joi.required().custom(objectId),
}),
body: Joi.object()
.keys({
email: Joi.string().email(),
password: Joi.string().custom(password),
name: Joi.string(),
})
.min(1),
};
const deleteUser = {
params: Joi.object().keys({
userId: Joi.string().custom(objectId),
}),
};
module.exports = {
createUser,
getUsers,
getUser,
updateUser,
deleteUser,
};