Merge branch 'testing' into 'master'

feat: user email validation function

See merge request profile-image/kedaireka/polinema-adapative-learning/backend-adaptive-learning!5
This commit is contained in:
elangptra 2024-11-07 03:22:33 +00:00
commit 20069433eb
13 changed files with 760 additions and 68 deletions

View File

@ -10,6 +10,7 @@ DB_NAME = adaptive_learning_dev
ACCESS_TOKEN_SECRET = adamSacredToken
REFRESH_TOKEN_SECRET = adamRefreshToken
RESET_PASSWORD_SECRET = adamResetToken
VERIFY_TOKEN_SECRET = adamVerifyToken
EMAIL_USER = 2141720057@student.polinema.ac.id
EMAIL_PASS = bfcrcrtjbefcveuv

View File

@ -10,6 +10,7 @@ DB_NAME = adaptive_learning_dev
ACCESS_TOKEN_SECRET = # secret code untuk generate access token
REFRESH_TOKEN_SECRET = # secret code untuk generate refresh token
RESET_PASSWORD_SECRET = # secret code reset password
VERIFY_TOKEN_SECRET = # secret code verify token
EMAIL_USER = # alamat email yang mengirimkan token reset password
EMAIL_PASS = # pass alamat email

View File

@ -10,6 +10,7 @@ DB_NAME = adaptive_learning_prod
ACCESS_TOKEN_SECRET = adamSacredToken
REFRESH_TOKEN_SECRET = adamRefreshToken
RESET_PASSWORD_SECRET = adamResetToken
VERIFY_TOKEN_SECRET = adamVerifyToken
EMAIL_USER = 2141720057@student.polinema.ac.id
EMAIL_PASS = bfcrcrtjbefcveuv

View File

@ -10,6 +10,7 @@ DB_NAME = adaptive_learning_prod
ACCESS_TOKEN_SECRET = # secret code untuk generate access token
REFRESH_TOKEN_SECRET = # secret code untuk generate refresh token
RESET_PASSWORD_SECRET = # secret code reset password
VERIFY_TOKEN_SECRET = # secret code verify token
EMAIL_USER = # alamat email yang mengirimkan token reset password
EMAIL_PASS = # pass alamat email

View File

@ -10,6 +10,7 @@ DB_NAME = adaptive_learning
ACCESS_TOKEN_SECRET =
REFRESH_TOKEN_SECRET =
RESET_PASSWORD_SECRET =
VERIFY_TOKEN_SECRET =
EMAIL_USER =
EMAIL_PASS =

View File

@ -104,6 +104,7 @@ export const registerTeacher = async (req, res) => {
EMAIL: EMAIL,
PASSWORD: hashedPassword,
ROLE: "teacher",
IS_VALIDATED: 0,
},
{ transaction }
);
@ -118,15 +119,148 @@ export const registerTeacher = async (req, res) => {
await transaction.commit();
const teacherResponse = {
ID: newUser.ID,
NAME_USERS: newUser.NAME_USERS,
EMAIL: newUser.EMAIL,
NIP: NIP,
ROLE: newUser.ROLE,
};
const token = jwt.sign(
{ userId: newUser.ID },
process.env.VERIFY_TOKEN_SECRET,
{ expiresIn: "1h" }
);
response(200, teacherResponse, "Teacher registration successful", res);
const validationLink = `${process.env.CLIENT_URL}/validate-email?token=${token}`;
await transporter.sendMail({
from: process.env.EMAIL_USER,
to: EMAIL,
subject: "Email Verification",
html: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Verification</title>
<style>
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Arial, sans-serif;
line-height: 1.6;
background-color: #f5f5f5;
}
.container {
max-width: 600px;
margin: 20px auto;
padding: 0;
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header {
background-color: #0090FF;
padding: 25px 20px;
text-align: center;
border-radius: 10px 10px 0 0;
}
.header h1 {
color: #ffffff;
margin: 0;
font-size: 32px;
letter-spacing: 2px;
font-weight: bold;
}
.content {
padding: 40px 30px;
background-color: #ffffff;
color: #333333;
}
.content h2 {
color: #0090FF;
margin-top: 0;
font-size: 24px;
margin-bottom: 20px;
}
.content p {
margin-bottom: 15px;
font-size: 16px;
line-height: 1.7;
}
.button {
display: inline-block;
padding: 12px 35px;
background-color: #0090FF;
color: #ffffff !important;
text-decoration: none;
border-radius: 5px;
margin: 25px 0;
font-size: 16px;
font-weight: bold;
text-align: center;
transition: background-color 0.3s ease;
box-shadow: 0 2px 5px rgba(0,144,255,0.3);
}
.button:hover {
background-color: #007acc;
}
.footer {
background-color: #0090FF;
color: #ffffff;
padding: 25px 20px;
text-align: center;
border-radius: 0 0 10px 10px;
font-size: 14px;
}
.signature {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
font-weight: bold;
color: #0090FF;
}
.important-note {
background-color: #f8f9fa;
padding: 15px;
border-left: 4px solid #0090FF;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>SEALS</h1>
</div>
<div class="content">
<h2>Hello, ${NAME_USERS}! 👋</h2>
<p>Welcome to SEALS! We're excited to have you on board.</p>
<p>To get started, please verify your email address by clicking the button below:</p>
<div style="text-align: center;">
<a href="${validationLink}" class="button">Verify Email</a>
</div>
<div class="important-note">
<p style="margin: 0;"><strong>Important:</strong> This verification link will expire in 1 hour. If you don't complete the verification within this time, you'll need to register again.</p>
</div>
<p>If you didn't create an account with SEALS, please ignore this email.</p>
<div class="signature">
<p>Thank you for choosing SEALS!</p>
<p style="margin: 5px 0 0 0;">The SEALS Team</p>
</div>
</div>
<div class="footer">
<p style="margin: 0;">&copy; 2024 SEALS. All rights reserved.</p>
</div>
</div>
</body>
</html>
`,
});
response(200, null, "Teacher registered! Please verify your email.", res);
} catch (error) {
console.log(error);
await transaction.rollback();
@ -186,6 +320,322 @@ export const registerStudent = async (req, res) => {
EMAIL: EMAIL,
PASSWORD: hashedPassword,
ROLE: "student",
IS_VALIDATED: 0,
},
{ transaction }
);
await models.Student.create(
{
ID: newUser.ID,
NISN: NISN,
},
{ transaction }
);
await transaction.commit();
const token = jwt.sign(
{ userId: newUser.ID },
process.env.VERIFY_TOKEN_SECRET,
{ expiresIn: "1h" }
);
const validationLink = `${process.env.CLIENT_URL}/validate-email?token=${token}`;
await transporter.sendMail({
from: process.env.EMAIL_USER,
to: EMAIL,
subject: "Email Verification",
html: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Email Verification</title>
<style>
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Arial, sans-serif;
line-height: 1.6;
background-color: #f5f5f5;
}
.container {
max-width: 600px;
margin: 20px auto;
padding: 0;
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header {
background-color: #0090FF;
padding: 25px 20px;
text-align: center;
border-radius: 10px 10px 0 0;
}
.header h1 {
color: #ffffff;
margin: 0;
font-size: 32px;
letter-spacing: 2px;
font-weight: bold;
}
.content {
padding: 40px 30px;
background-color: #ffffff;
color: #333333;
}
.content h2 {
color: #0090FF;
margin-top: 0;
font-size: 24px;
margin-bottom: 20px;
}
.content p {
margin-bottom: 15px;
font-size: 16px;
line-height: 1.7;
}
.button {
display: inline-block;
padding: 12px 35px;
background-color: #0090FF;
color: #ffffff !important;
text-decoration: none;
border-radius: 5px;
margin: 25px 0;
font-size: 16px;
font-weight: bold;
text-align: center;
transition: background-color 0.3s ease;
box-shadow: 0 2px 5px rgba(0,144,255,0.3);
}
.button:hover {
background-color: #007acc;
}
.footer {
background-color: #0090FF;
color: #ffffff;
padding: 25px 20px;
text-align: center;
border-radius: 0 0 10px 10px;
font-size: 14px;
}
.signature {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
font-weight: bold;
color: #0090FF;
}
.important-note {
background-color: #f8f9fa;
padding: 15px;
border-left: 4px solid #0090FF;
margin: 20px 0;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>SEALS</h1>
</div>
<div class="content">
<h2>Hello, ${NAME_USERS}! 👋</h2>
<p>Welcome to SEALS! We're excited to have you on board.</p>
<p>To get started, please verify your email address by clicking the button below:</p>
<div style="text-align: center;">
<a href="${validationLink}" class="button">Verify Email</a>
</div>
<div class="important-note">
<p style="margin: 0;"><strong>Important:</strong> This verification link will expire in 1 hour. If you don't complete the verification within this time, you'll need to register again.</p>
</div>
<p>If you didn't create an account with SEALS, please ignore this email.</p>
<div class="signature">
<p>Thank you for choosing SEALS!</p>
<p style="margin: 5px 0 0 0;">The SEALS Team</p>
</div>
</div>
<div class="footer">
<p style="margin: 0;">&copy; 2024 SEALS. All rights reserved.</p>
</div>
</div>
</body>
</html>
`,
});
response(200, null, "Student registered! Please verify your email.", res);
} catch (error) {
console.log(error);
await transaction.rollback();
if (error.name === "SequelizeUniqueConstraintError") {
const field = error.original.sqlMessage.match(/for key '(.+)'/)[1];
if (field === "student_unique_nisn") {
return response(400, null, "NISN already registered!", res);
}
if (field === "user_unique_email") {
return response(400, null, "Email already registered!", res);
}
}
response(500, null, "Internal Server Error", res);
}
};
export const validateEmail = async (req, res) => {
const { TOKEN } = req.body;
try {
const decoded = jwt.verify(TOKEN, process.env.VERIFY_TOKEN_SECRET);
const userId = decoded.userId;
await models.User.update({ IS_VALIDATED: 1 }, { where: { ID: userId } });
response(200, null, "Email successfully validated!", res);
} catch (error) {
response(400, null, "Invalid or expired token", res);
}
};
export const registerTeacherForAdmin = async (req, res) => {
const { NAME_USERS, EMAIL, NIP, PASSWORD, CONFIRM_PASSWORD } = req.body;
if (!NAME_USERS) {
return response(400, null, "Name is required!", res);
}
if (!EMAIL) {
return response(400, null, "Email is required!", res);
}
if (!NIP) {
return response(400, null, "NIP is required for teachers!", res);
}
if (!PASSWORD) {
return response(400, null, "Password is required!", res);
}
if (!CONFIRM_PASSWORD) {
return response(400, null, "Confirm Password is required!", res);
}
if (PASSWORD !== CONFIRM_PASSWORD) {
return response(400, null, "Passwords do not match!", res);
}
const transaction = await models.db.transaction();
try {
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(PASSWORD, salt);
const newUser = await models.User.create(
{
NAME_USERS: NAME_USERS,
EMAIL: EMAIL,
PASSWORD: hashedPassword,
ROLE: "teacher",
IS_VALIDATED: 1,
},
{ transaction }
);
await models.Teacher.create(
{
ID: newUser.ID,
NIP: NIP,
},
{ transaction }
);
await transaction.commit();
const teacherResponse = {
ID: newUser.ID,
NAME_USERS: newUser.NAME_USERS,
EMAIL: newUser.EMAIL,
NIP: NIP,
ROLE: newUser.ROLE,
IS_VALIDATED: newUser.IS_VALIDATED,
};
response(200, teacherResponse, "Teacher registration successful", res);
} catch (error) {
console.log(error);
await transaction.rollback();
if (error.name === "SequelizeUniqueConstraintError") {
const field = error.original.sqlMessage.match(/for key '(.+)'/)[1];
if (field === "teacher_unique_nip") {
return response(400, null, "NIP already registered!", res);
}
if (field === "user_unique_email") {
return response(400, null, "Email already registered!", res);
}
}
response(500, null, "Internal Server Error", res);
}
};
export const registerStudentForAdmin = async (req, res) => {
const { NAME_USERS, EMAIL, NISN, PASSWORD, CONFIRM_PASSWORD } = req.body;
if (!NAME_USERS) {
return response(400, null, "Name is required!", res);
}
if (!EMAIL) {
return response(400, null, "Email is required!", res);
}
if (!NISN) {
return response(400, null, "NISN is required for students!", res);
}
if (!PASSWORD) {
return response(400, null, "Password is required!", res);
}
if (!CONFIRM_PASSWORD) {
return response(400, null, "Confirm Password is required!", res);
}
if (PASSWORD !== CONFIRM_PASSWORD) {
return response(400, null, "Passwords do not match!", res);
}
const transaction = await models.db.transaction();
try {
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(PASSWORD, salt);
const newUser = await models.User.create(
{
NAME_USERS: NAME_USERS,
EMAIL: EMAIL,
PASSWORD: hashedPassword,
ROLE: "student",
IS_VALIDATED: 1,
},
{ transaction }
);
@ -206,6 +656,7 @@ export const registerStudent = async (req, res) => {
EMAIL: newUser.EMAIL,
NISN: NISN,
ROLE: newUser.ROLE,
IS_VALIDATED: newUser.IS_VALIDATED,
};
response(200, studentResponse, "Student registration successful", res);
@ -247,6 +698,10 @@ export const loginUser = async (req, res) => {
return response(404, null, "User data not found!", res);
}
if (user.IS_VALIDATED !== 1) {
return response(403, null, "User is not validated! Please verify your email first.", res);
}
const validPassword = await bcrypt.compare(PASSWORD, user.PASSWORD);
if (!validPassword) {
return response(401, null, "The password you entered is incorrect!", res);
@ -272,8 +727,7 @@ export const loginUser = async (req, res) => {
res.cookie("refreshToken", refreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
// sameSite: "Strict",
maxAge: 7 * 24 * 60 * 60 * 1000
maxAge: 7 * 24 * 60 * 60 * 1000,
});
const userResponse = {
@ -347,8 +801,8 @@ export const refreshToken = async (req, res) => {
res.cookie("refreshToken", newRefreshToken, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
// sameSite: "Strict",
maxAge: 7 * 24 * 60 * 60 * 1000
maxAge: 7 * 24 * 60 * 60 * 1000,
});
response(
@ -395,10 +849,143 @@ export const forgotPassword = async (req, res) => {
from: process.env.EMAIL_USER,
to: user.EMAIL,
subject: "Password Reset",
text: `You are receiving this because you (or someone else) have requested the reset of the password for your account.
Please click on the following link, or paste this into your browser to complete the process:
${resetLink}
If you did not request this, please ignore this email and your password will remain unchanged.`,
html: `
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Reset Password</title>
<style>
body {
margin: 0;
padding: 0;
font-family: 'Segoe UI', Arial, sans-serif;
line-height: 1.6;
background-color: #f5f5f5;
}
.container {
max-width: 600px;
margin: 20px auto;
padding: 0;
background-color: #ffffff;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.header {
background-color: #0090FF;
padding: 25px 20px;
text-align: center;
border-radius: 10px 10px 0 0;
}
.header h1 {
color: #ffffff;
margin: 0;
font-size: 32px;
letter-spacing: 2px;
font-weight: bold;
}
.content {
padding: 40px 30px;
background-color: #ffffff;
color: #333333;
}
.content h2 {
color: #0090FF;
margin-top: 0;
font-size: 24px;
margin-bottom: 20px;
}
.content p {
margin-bottom: 15px;
font-size: 16px;
line-height: 1.7;
}
.button {
display: inline-block;
padding: 12px 35px;
background-color: #0090FF;
color: #ffffff !important;
text-decoration: none;
border-radius: 5px;
margin: 25px 0;
font-size: 16px;
font-weight: bold;
text-align: center;
transition: background-color 0.3s ease;
box-shadow: 0 2px 5px rgba(0,144,255,0.3);
}
.button:hover {
background-color: #007acc;
}
.footer {
background-color: #0090FF;
color: #ffffff;
padding: 25px 20px;
text-align: center;
border-radius: 0 0 10px 10px;
font-size: 14px;
}
.signature {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #eee;
font-weight: bold;
color: #0090FF;
}
.important-note {
background-color: #f8f9fa;
padding: 15px;
border-left: 4px solid #0090FF;
margin: 20px 0;
}
.security-warning {
background-color: #fff3cd;
padding: 15px;
border-left: 4px solid #ffc107;
margin: 20px 0;
font-size: 14px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>SEALS</h1>
</div>
<div class="content">
<h2>Password Reset Request</h2>
<p>Hello!</p>
<p>We received a request to reset the password for your SEALS account. To proceed with the password reset, please click the button below:</p>
<div style="text-align: center;">
<a href="${resetLink}" class="button">Reset Password</a>
</div>
<div class="important-note">
<p style="margin: 0;"><strong>Important:</strong> This reset password link will expire in 1 hour. Please reset your password as soon as possible to maintain access to your account.</p>
</div>
<div class="security-warning">
<p style="margin: 0;"> If you didn't request a password reset, please ignore this email or contact our support team if you have concerns about your account's security.</p>
</div>
<div class="signature">
<p>Thank you for using SEALS!</p>
<p style="margin: 5px 0 0 0;">The SEALS Team</p>
</div>
</div>
<div class="footer">
<p style="margin: 0;">&copy; 2024 SEALS. All rights reserved.</p>
</div>
</div>
</body>
</html>
`,
};
await transporter.sendMail(mailOptions);

View File

@ -29,6 +29,11 @@ module.exports = {
type: Sequelize.STRING(1024),
allowNull: true,
},
IS_VALIDATED: {
type: Sequelize.TINYINT(1),
allowNull: true,
defaultValue: 0,
},
REFRESH_TOKEN: {
type: Sequelize.STRING(256),
allowNull: true,

View File

@ -12,6 +12,7 @@ module.exports = {
EMAIL: "adminseals@gmail.com",
PASSWORD: adminHashedPassword,
ROLE: "admin",
IS_VALIDATED: 1,
TIME_USERS: new Date(),
},
{
@ -20,6 +21,7 @@ module.exports = {
EMAIL: "sealsteach@gmail.com",
PASSWORD: teacherHashedPassword,
ROLE: "teacher",
IS_VALIDATED: 1,
TIME_USERS: new Date(),
},
]);

View File

@ -5,6 +5,8 @@ import { testConnection } from "./database/db.js";
import router from "./routes/index.js";
import cookieParser from "cookie-parser";
import promBundle from "express-prom-bundle";
import cron from "node-cron";
import models from "./models/index.js";
dotenv.config();
const app = express();
@ -35,6 +37,54 @@ app.use(express.urlencoded({ extended: true }));
app.use(router);
app.use(express.static("public"));
cron.schedule("0 0 * * *", async () => {
const oneDayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000);
const transaction = await models.db.transaction();
try {
const unvalidatedUsers = await models.User.findAll({
attributes: ["ID"],
where: {
IS_VALIDATED: 0,
TIME_USERS: { [models.Sequelize.Op.lt]: oneDayAgo },
},
transaction,
});
const unvalidatedUserIDs = unvalidatedUsers.map((user) => user.ID);
if (unvalidatedUserIDs.length > 0) {
await models.Student.destroy({
where: { ID: { [models.Sequelize.Op.in]: unvalidatedUserIDs } },
transaction,
});
await models.Teacher.destroy({
where: { ID: { [models.Sequelize.Op.in]: unvalidatedUserIDs } },
transaction,
});
await models.User.destroy({
where: {
ID: { [models.Sequelize.Op.in]: unvalidatedUserIDs },
},
transaction,
});
}
await transaction.commit();
console.log(
"Removed unvalidated users and their related data registered over 1 day ago."
);
} catch (error) {
await transaction.rollback();
console.error(
"Failed to delete unvalidated users and related data:",
error
);
}
});
app.listen(process.env.APP_PORT, () => {
testConnection();
console.log(`Server running on port ${process.env.APP_PORT}`);

View File

@ -41,6 +41,11 @@ const UserModel = (DataTypes) => {
type: DataTypes.STRING(1024),
allowNull: true,
},
IS_VALIDATED: {
type: DataTypes.TINYINT(1),
allowNull: true,
defaultValue: 0,
},
REFRESH_TOKEN: {
type: DataTypes.STRING(256),
allowNull: true,

133
package-lock.json generated
View File

@ -20,6 +20,7 @@
"moment-timezone": "^0.5.45",
"multer": "^1.4.5-lts.1",
"mysql2": "^3.11.0",
"node-cron": "^3.0.3",
"nodemailer": "^6.9.14",
"nodemon": "^3.1.4",
"prom-client": "^15.1.3",
@ -347,9 +348,9 @@
"integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
},
"node_modules/body-parser": {
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"version": "1.20.3",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
"integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
@ -359,7 +360,7 @@
"http-errors": "2.0.0",
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"qs": "6.13.0",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
@ -583,19 +584,19 @@
}
},
"node_modules/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
"version": "0.7.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz",
"integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cookie-parser": {
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
"version": "1.4.7",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz",
"integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==",
"dependencies": {
"cookie": "0.4.1",
"cookie": "0.7.2",
"cookie-signature": "1.0.6"
},
"engines": {
@ -782,9 +783,9 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"engines": {
"node": ">= 0.8"
}
@ -901,36 +902,36 @@
}
},
"node_modules/express": {
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"version": "4.21.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
"integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.2",
"body-parser": "1.20.3",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.6.0",
"cookie": "0.7.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"finalhandler": "1.2.0",
"finalhandler": "1.3.1",
"fresh": "0.5.2",
"http-errors": "2.0.0",
"merge-descriptors": "1.0.1",
"merge-descriptors": "1.0.3",
"methods": "~1.1.2",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
"path-to-regexp": "0.1.7",
"path-to-regexp": "0.1.10",
"proxy-addr": "~2.0.7",
"qs": "6.11.0",
"qs": "6.13.0",
"range-parser": "~1.2.1",
"safe-buffer": "5.2.1",
"send": "0.18.0",
"serve-static": "1.15.0",
"send": "0.19.0",
"serve-static": "1.16.2",
"setprototypeof": "1.2.0",
"statuses": "2.0.1",
"type-is": "~1.6.18",
@ -958,9 +959,9 @@
}
},
"node_modules/express/node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
"integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
"engines": {
"node": ">= 0.6"
}
@ -985,12 +986,12 @@
}
},
"node_modules/finalhandler": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz",
"integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
"integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"on-finished": "2.4.1",
"parseurl": "~1.3.3",
@ -1585,9 +1586,12 @@
}
},
"node_modules/merge-descriptors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz",
"integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w=="
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
"integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/methods": {
"version": "1.1.2",
@ -1768,6 +1772,25 @@
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ=="
},
"node_modules/node-cron": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz",
"integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==",
"dependencies": {
"uuid": "8.3.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/node-cron/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/nodemailer": {
"version": "6.9.14",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.14.tgz",
@ -1909,9 +1932,9 @@
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="
},
"node_modules/path-to-regexp": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz",
"integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
"version": "0.1.10",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
"integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
},
"node_modules/pg-connection-string": {
"version": "2.6.4",
@ -1969,11 +1992,11 @@
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w=="
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.4"
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
@ -2080,9 +2103,9 @@
}
},
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
"integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
"version": "0.19.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
"integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
@ -2102,6 +2125,14 @@
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@ -2232,14 +2263,14 @@
}
},
"node_modules/serve-static": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
"integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
"integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
"dependencies": {
"encodeurl": "~1.0.2",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "0.18.0"
"send": "0.19.0"
},
"engines": {
"node": ">= 0.8.0"

View File

@ -32,6 +32,7 @@
"moment-timezone": "^0.5.45",
"multer": "^1.4.5-lts.1",
"mysql2": "^3.11.0",
"node-cron": "^3.0.3",
"nodemailer": "^6.9.14",
"nodemon": "^3.1.4",
"prom-client": "^15.1.3",

View File

@ -1,5 +1,5 @@
import express from "express";
import { registerTeacher, registerStudent, registerAdmin, loginUser, refreshToken, logoutUser, forgotPassword, resetPassword } from "../../controllers/auth/auth.js";
import { registerTeacher, registerStudent, registerStudentForAdmin, registerTeacherForAdmin, registerAdmin, validateEmail, loginUser, refreshToken, logoutUser, forgotPassword, resetPassword } from "../../controllers/auth/auth.js";
import { verifyLoginUser, adminOnly } from "../../middlewares/User/authUser.js";
const router = express.Router();
@ -8,8 +8,14 @@ router.post("/register/teacher", registerTeacher);
router.post("/register/student", registerStudent);
router.post("/admin/register/teacher", verifyLoginUser, adminOnly, registerTeacherForAdmin);
router.post("/admin/register/student", verifyLoginUser, adminOnly, registerStudentForAdmin);
router.post("/register/admin", verifyLoginUser, adminOnly, registerAdmin);
router.post("/validateEmail", validateEmail);
router.post("/login", loginUser);
router.post("/refreshToken", refreshToken);