diff --git a/.env.development b/.env.development
index 1860359..8e7dc1d 100644
--- a/.env.development
+++ b/.env.development
@@ -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
\ No newline at end of file
diff --git a/.env.developmentexample b/.env.developmentexample
index 80ce7c3..b2baa21 100644
--- a/.env.developmentexample
+++ b/.env.developmentexample
@@ -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
\ No newline at end of file
diff --git a/.env.production b/.env.production
index 99a7f98..8b7820c 100644
--- a/.env.production
+++ b/.env.production
@@ -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
\ No newline at end of file
diff --git a/.env.productionexample b/.env.productionexample
index 4bf9525..27589da 100644
--- a/.env.productionexample
+++ b/.env.productionexample
@@ -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
\ No newline at end of file
diff --git a/.envexample b/.envexample
index f5f47b6..4391e10 100644
--- a/.envexample
+++ b/.envexample
@@ -10,6 +10,7 @@ DB_NAME = adaptive_learning
ACCESS_TOKEN_SECRET =
REFRESH_TOKEN_SECRET =
RESET_PASSWORD_SECRET =
+VERIFY_TOKEN_SECRET =
EMAIL_USER =
EMAIL_PASS =
\ No newline at end of file
diff --git a/controllers/auth/auth.js b/controllers/auth/auth.js
index 7f80fad..7e44a69 100644
--- a/controllers/auth/auth.js
+++ b/controllers/auth/auth.js
@@ -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: `
+
+
+
+
+
+ Email Verification
+
+
+
+
+
+
+
+
Hello, ${NAME_USERS}! 👋
+
+
Welcome to SEALS! We're excited to have you on board.
+
+
To get started, please verify your email address by clicking the button below:
+
+
+
+
+
Important: This verification link will expire in 1 hour. If you don't complete the verification within this time, you'll need to register again.
+
+
+
If you didn't create an account with SEALS, please ignore this email.
+
+
+
Thank you for choosing SEALS!
+
The SEALS Team
+
+
+
+
+
+
+
+ `,
+ });
+
+ 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: `
+
+
+
+
+
+ Email Verification
+
+
+
+
+
+
+
+
Hello, ${NAME_USERS}! 👋
+
+
Welcome to SEALS! We're excited to have you on board.
+
+
To get started, please verify your email address by clicking the button below:
+
+
+
+
+
Important: This verification link will expire in 1 hour. If you don't complete the verification within this time, you'll need to register again.
+
+
+
If you didn't create an account with SEALS, please ignore this email.
+
+
+
Thank you for choosing SEALS!
+
The SEALS Team
+
+
+
+
+
+
+
+ `,
+ });
+
+ 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: `
+
+
+
+
+
+ Reset Password
+
+
+
+
+
+
+
+
Password Reset Request
+
+
Hello!
+
+
We received a request to reset the password for your SEALS account. To proceed with the password reset, please click the button below:
+
+
+
+
+
Important: This reset password link will expire in 1 hour. Please reset your password as soon as possible to maintain access to your account.
+
+
+
+
⚠️ 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.
+
+
+
+
Thank you for using SEALS!
+
The SEALS Team
+
+
+
+
+
+
+
+ `,
};
await transporter.sendMail(mailOptions);
diff --git a/database/migrations/20241012202559-create-users.cjs b/database/migrations/20241012202559-create-users.cjs
index e4e5343..2797066 100644
--- a/database/migrations/20241012202559-create-users.cjs
+++ b/database/migrations/20241012202559-create-users.cjs
@@ -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,
diff --git a/database/seeders/20241012193900-create-initial-user.cjs b/database/seeders/20241012193900-create-initial-user.cjs
index 5140035..51e7af5 100644
--- a/database/seeders/20241012193900-create-initial-user.cjs
+++ b/database/seeders/20241012193900-create-initial-user.cjs
@@ -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(),
},
]);
diff --git a/index.js b/index.js
index 2b4e42f..7fac4df 100644
--- a/index.js
+++ b/index.js
@@ -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}`);
diff --git a/models/usersModels/userModel.js b/models/usersModels/userModel.js
index f314ce7..eaf02c6 100644
--- a/models/usersModels/userModel.js
+++ b/models/usersModels/userModel.js
@@ -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,
diff --git a/package-lock.json b/package-lock.json
index 73c7f5d..fa92b99 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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"
diff --git a/package.json b/package.json
index 0efca83..ab0f3aa 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/routes/auth/auth.js b/routes/auth/auth.js
index aec5376..6ffea2f 100644
--- a/routes/auth/auth.js
+++ b/routes/auth/auth.js
@@ -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);