GWM : password reset and QuarterlyWindowsConfiguration
This commit is contained in:
parent
59975c709a
commit
b0c7e1c8d3
@ -54,13 +54,15 @@ exports.getUserById = async (req, res) => {
|
|||||||
// Update user
|
// Update user
|
||||||
exports.updateUser = async (req, res) => {
|
exports.updateUser = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const { name, email, password, is_active } = req.body;
|
const { name, email, is_active, password } = req.body;
|
||||||
const data = {};
|
const data = {};
|
||||||
if (name) data.name = name;
|
|
||||||
if (email) data.email = email;
|
// Allow updates only if fields are provided
|
||||||
if (is_active !== undefined) data.is_active = is_active;
|
if (name !== undefined && name !== null) data.name = name;
|
||||||
if (password) data.password = await bcrypt.hash(password, 10);
|
if (email !== undefined && email !== null) data.email = email;
|
||||||
|
if (is_active !== undefined && is_active !== null) data.is_active = is_active;
|
||||||
|
if (password && password.trim() !== "") data.password = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
const [updated] = await EstablishmentUser.update(data, {
|
const [updated] = await EstablishmentUser.update(data, {
|
||||||
where: { id: req.params.id },
|
where: { id: req.params.id },
|
||||||
@ -75,6 +77,48 @@ exports.updateUser = async (req, res) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Change Establishment User Password
|
||||||
|
exports.changeUserPassword = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { old_password, new_password, confirm_password } = req.body;
|
||||||
|
const userId = req.params.id;
|
||||||
|
|
||||||
|
// Validate inputs
|
||||||
|
if (!old_password || !new_password || !confirm_password) {
|
||||||
|
return res.status(400).send({ status: "failed", message: "All password fields are required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_password !== confirm_password) {
|
||||||
|
return res.status(400).send({ status: "failed", message: "New password and confirm password do not match" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find user
|
||||||
|
const user = await EstablishmentUser.findByPk(userId);
|
||||||
|
if (!user) {
|
||||||
|
return res.status(404).send({ status: "failed", message: "User not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify old password
|
||||||
|
const isMatch = await bcrypt.compare(old_password, user.password);
|
||||||
|
if (!isMatch) {
|
||||||
|
return res.status(400).send({ status: "failed", message: "Old password is incorrect" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash and update new password
|
||||||
|
const hashedPassword = await bcrypt.hash(new_password, 10);
|
||||||
|
await EstablishmentUser.update(
|
||||||
|
{ password: hashedPassword },
|
||||||
|
{ where: { id: userId } }
|
||||||
|
);
|
||||||
|
|
||||||
|
return res.status(200).send({status: "success", message: "Password updated successfully",});
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
return res.status(500).send({ status: "failed", message: err.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
// Delete user
|
// Delete user
|
||||||
exports.deleteUser = async (req, res) => {
|
exports.deleteUser = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
85
app/controllers/quarterlyWindowsConfiguration.controller.js
Normal file
85
app/controllers/quarterlyWindowsConfiguration.controller.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
const db = require("../models");
|
||||||
|
const QuarterlyWindowsConfiguration = db.QuarterlyWindowsConfiguration;
|
||||||
|
|
||||||
|
// Create new configuration
|
||||||
|
exports.createConfig = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const data = await QuarterlyWindowsConfiguration.create(req.body);
|
||||||
|
res.status(201).send({
|
||||||
|
status: "success",
|
||||||
|
message: "Quarterly window configuration created successfully",
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).send({ status: "failed", message: err.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get all configurations
|
||||||
|
exports.getAllConfigs = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const data = await QuarterlyWindowsConfiguration.findAll({
|
||||||
|
order: [["year", "DESC"]],
|
||||||
|
});
|
||||||
|
res
|
||||||
|
.status(200)
|
||||||
|
.send({ status: "success", message: "Fetched successfully", data });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).send({ status: "failed", message: err.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get single configuration by ID
|
||||||
|
exports.getConfigById = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const data = await QuarterlyWindowsConfiguration.findByPk(req.params.id);
|
||||||
|
if (!data)
|
||||||
|
return res
|
||||||
|
.status(404)
|
||||||
|
.send({ status: "failed", message: "Record not found" });
|
||||||
|
|
||||||
|
res
|
||||||
|
.status(200)
|
||||||
|
.send({ status: "success", message: "Fetched successfully", data });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).send({ status: "failed", message: err.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update configuration
|
||||||
|
exports.updateConfig = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const [updated] = await QuarterlyWindowsConfiguration.update(req.body, {
|
||||||
|
where: { id: req.params.id },
|
||||||
|
});
|
||||||
|
if (!updated)
|
||||||
|
return res
|
||||||
|
.status(404)
|
||||||
|
.send({ status: "failed", message: "Record not found" });
|
||||||
|
|
||||||
|
res
|
||||||
|
.status(200)
|
||||||
|
.send({ status: "success", message: "Updated successfully" });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).send({ status: "failed", message: err.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Delete configuration
|
||||||
|
exports.deleteConfig = async (req, res) => {
|
||||||
|
try {
|
||||||
|
const deleted = await QuarterlyWindowsConfiguration.destroy({
|
||||||
|
where: { id: req.params.id },
|
||||||
|
});
|
||||||
|
if (!deleted)
|
||||||
|
return res
|
||||||
|
.status(404)
|
||||||
|
.send({ status: "failed", message: "Record not found" });
|
||||||
|
|
||||||
|
res
|
||||||
|
.status(200)
|
||||||
|
.send({ status: "success", message: "Deleted successfully" });
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).send({ status: "failed", message: err.message });
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -3,6 +3,7 @@ const Submission = db.Submission;
|
|||||||
const SubmissionProduct = db.SubmissionProduct;
|
const SubmissionProduct = db.SubmissionProduct;
|
||||||
const Product = db.Product;
|
const Product = db.Product;
|
||||||
const { Op } = require("sequelize");
|
const { Op } = require("sequelize");
|
||||||
|
const { sendEmail } = require("../services/email.service");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -24,6 +24,7 @@ db.SubmissionProduct = require("./submissionProduct.model")(sequelize, DataTypes
|
|||||||
db.UnitMaster = require("./UnitMaster.model")(sequelize, DataTypes);
|
db.UnitMaster = require("./UnitMaster.model")(sequelize, DataTypes);
|
||||||
db.SubmissionDeadline = require("./submissionDeadline.model")(sequelize, DataTypes);
|
db.SubmissionDeadline = require("./submissionDeadline.model")(sequelize, DataTypes);
|
||||||
db.NotificationTemplate = require("./notificationTemplate.model")(sequelize, DataTypes);
|
db.NotificationTemplate = require("./notificationTemplate.model")(sequelize, DataTypes);
|
||||||
|
db.QuarterlyWindowsConfiguration = require("./quarterlyWindowsConfiguration.model")(sequelize, DataTypes);
|
||||||
|
|
||||||
|
|
||||||
// Associations
|
// Associations
|
||||||
|
|||||||
59
app/models/quarterlyWindowsConfiguration.model.js
Normal file
59
app/models/quarterlyWindowsConfiguration.model.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
module.exports = (sequelize, DataTypes) => {
|
||||||
|
const QuarterlyWindowsConfiguration = sequelize.define(
|
||||||
|
"quarterly_windows_configuration_master",
|
||||||
|
{
|
||||||
|
id: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
autoIncrement: true,
|
||||||
|
primaryKey: true,
|
||||||
|
},
|
||||||
|
year: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
quarter: {
|
||||||
|
type: DataTypes.STRING(10),
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
start_date: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
end_date: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: false,
|
||||||
|
},
|
||||||
|
grace_periods_days: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
is_active: {
|
||||||
|
type: DataTypes.BOOLEAN,
|
||||||
|
defaultValue: true,
|
||||||
|
},
|
||||||
|
created_at: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
defaultValue: DataTypes.NOW,
|
||||||
|
},
|
||||||
|
created_by: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
updated_at: {
|
||||||
|
type: DataTypes.DATE,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
updated_by: {
|
||||||
|
type: DataTypes.INTEGER,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
tableName: "quarterly_windows_configuration_master",
|
||||||
|
timestamps: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return QuarterlyWindowsConfiguration;
|
||||||
|
};
|
||||||
|
|
||||||
@ -16,6 +16,7 @@ const submissionController = require("../controllers/submission.controller");
|
|||||||
const ConfigController = require("../controllers/config.controller");
|
const ConfigController = require("../controllers/config.controller");
|
||||||
const dashboardController = require("../controllers/dashboard.controller");
|
const dashboardController = require("../controllers/dashboard.controller");
|
||||||
const notificationTemplateController = require("../controllers/notificationTemplate.controller");
|
const notificationTemplateController = require("../controllers/notificationTemplate.controller");
|
||||||
|
const quarterlyWindowsController = require("../controllers/quarterlyWindowsConfiguration.controller");
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -48,6 +49,8 @@ const notificationTemplateController = require("../controllers/notificationTempl
|
|||||||
* description:
|
* description:
|
||||||
* - name: Notification Templates
|
* - name: Notification Templates
|
||||||
* description: Manage notification templates
|
* description: Manage notification templates
|
||||||
|
* - name: Quarterly Windows Configuration
|
||||||
|
* description: Manage quarterly windows configuration master data
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
@ -652,6 +655,55 @@ router.get("/establishment-users/:id",[verifySignature, verifyToken], establishm
|
|||||||
*/
|
*/
|
||||||
router.put("/establishment-users/:id",[verifySignature, verifyToken], establishmentUserController.updateUser);
|
router.put("/establishment-users/:id",[verifySignature, verifyToken], establishmentUserController.updateUser);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/establishment-users/{id}/change-password:
|
||||||
|
* put:
|
||||||
|
* summary: Change Establishment User Password
|
||||||
|
* description: Allows an establishment user to change their password after verifying the old password.
|
||||||
|
* tags: [Establishment Users]
|
||||||
|
* security:
|
||||||
|
* - bearerAuth: []
|
||||||
|
* parameters:
|
||||||
|
* - name: id
|
||||||
|
* in: path
|
||||||
|
* required: true
|
||||||
|
* description: ID of the establishment user
|
||||||
|
* schema:
|
||||||
|
* type: integer
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* required:
|
||||||
|
* - old_password
|
||||||
|
* - new_password
|
||||||
|
* - confirm_password
|
||||||
|
* properties:
|
||||||
|
* old_password:
|
||||||
|
* type: string
|
||||||
|
* example: "OldPassword@123"
|
||||||
|
* new_password:
|
||||||
|
* type: string
|
||||||
|
* example: "NewPassword@123"
|
||||||
|
* confirm_password:
|
||||||
|
* type: string
|
||||||
|
* example: "NewPassword@123"
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Password updated successfully
|
||||||
|
* 400:
|
||||||
|
* description: Validation or password mismatch error
|
||||||
|
* 404:
|
||||||
|
* description: User not found
|
||||||
|
* 500:
|
||||||
|
* description: Internal server error
|
||||||
|
*/
|
||||||
|
router.put("/establishment-users/:id/change-password", establishmentUserController.changeUserPassword);
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
* /api/establishment-users/{id}:
|
* /api/establishment-users/{id}:
|
||||||
@ -1832,11 +1884,14 @@ router.get("/establishment_dashboard",[verifySignature, verifyToken],dashboardCo
|
|||||||
* get:
|
* get:
|
||||||
* summary: Get all notification templates
|
* summary: Get all notification templates
|
||||||
* tags: [Notification Templates]
|
* tags: [Notification Templates]
|
||||||
|
* security:
|
||||||
|
* - appSignature: []
|
||||||
|
* - bearerAuth: []
|
||||||
* responses:
|
* responses:
|
||||||
* 200:
|
* 200:
|
||||||
* description: Fetched successfully
|
* description: Fetched successfully
|
||||||
*/
|
*/
|
||||||
router.get("/notification_templates", notificationTemplateController.getAllTemplates);
|
router.get("/notification_templates",[verifySignature, verifyToken], notificationTemplateController.getAllTemplates);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
@ -1844,6 +1899,9 @@ router.get("/notification_templates", notificationTemplateController.getAllTempl
|
|||||||
* get:
|
* get:
|
||||||
* summary: Get a single notification template
|
* summary: Get a single notification template
|
||||||
* tags: [Notification Templates]
|
* tags: [Notification Templates]
|
||||||
|
* security:
|
||||||
|
* - appSignature: []
|
||||||
|
* - bearerAuth: []
|
||||||
* parameters:
|
* parameters:
|
||||||
* - name: id
|
* - name: id
|
||||||
* in: path
|
* in: path
|
||||||
@ -1852,31 +1910,9 @@ router.get("/notification_templates", notificationTemplateController.getAllTempl
|
|||||||
* 200:
|
* 200:
|
||||||
* description: Fetched successfully
|
* description: Fetched successfully
|
||||||
*/
|
*/
|
||||||
router.get("/notification_templates/:id", notificationTemplateController.getTemplateById);
|
router.get("/notification_templates/:id",[verifySignature, verifyToken], notificationTemplateController.getTemplateById);
|
||||||
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * @swagger
|
|
||||||
// * /api/notification_templates:
|
|
||||||
// * post:
|
|
||||||
// * summary: Create a new notification template
|
|
||||||
// * tags: [Notification Templates]
|
|
||||||
// * requestBody:
|
|
||||||
// * required: true
|
|
||||||
// * content:
|
|
||||||
// * application/json:
|
|
||||||
// * schema:
|
|
||||||
// * type: object
|
|
||||||
// * properties:
|
|
||||||
// * title: { type: string }
|
|
||||||
// * template_code: { type: string }
|
|
||||||
// * subject: { type: string }
|
|
||||||
// * body_html: { type: string }
|
|
||||||
// * placeholders: { type: array, items: { type: string } }
|
|
||||||
// * responses:
|
|
||||||
// * 201:
|
|
||||||
// * description: Created successfully
|
|
||||||
// */
|
|
||||||
// router.post("/notification_templates", notificationTemplateController.createTemplate);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
@ -1884,6 +1920,9 @@ router.get("/notification_templates/:id", notificationTemplateController.getTemp
|
|||||||
* put:
|
* put:
|
||||||
* summary: Update a notification template
|
* summary: Update a notification template
|
||||||
* tags: [Notification Templates]
|
* tags: [Notification Templates]
|
||||||
|
* security:
|
||||||
|
* - appSignature: []
|
||||||
|
* - bearerAuth: []
|
||||||
* parameters:
|
* parameters:
|
||||||
* - name: id
|
* - name: id
|
||||||
* in: path
|
* in: path
|
||||||
@ -1903,23 +1942,113 @@ router.get("/notification_templates/:id", notificationTemplateController.getTemp
|
|||||||
* 200:
|
* 200:
|
||||||
* description: Updated successfully
|
* description: Updated successfully
|
||||||
*/
|
*/
|
||||||
router.put("/notification_templates/:id", notificationTemplateController.updateTemplate);
|
router.put("/notification_templates/:id",[verifySignature, verifyToken], notificationTemplateController.updateTemplate);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/quarterly_windows:
|
||||||
|
* post:
|
||||||
|
* summary: Create new quarterly configuration
|
||||||
|
* tags: [Quarterly Windows Configuration]
|
||||||
|
* security:
|
||||||
|
* - appSignature: []
|
||||||
|
* - bearerAuth: []
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* properties:
|
||||||
|
* year: { type: integer }
|
||||||
|
* quarter: { type: string, example: "Q1" }
|
||||||
|
* start_date: { type: string, format: date }
|
||||||
|
* end_date: { type: string, format: date }
|
||||||
|
* grace_periods_days: { type: integer }
|
||||||
|
* is_active: { type: boolean }
|
||||||
|
* responses:
|
||||||
|
* 201:
|
||||||
|
* description: Created successfully
|
||||||
|
*/
|
||||||
|
router.post("/quarterly_windows",[verifySignature, verifyToken], quarterlyWindowsController.createConfig);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/quarterly_windows:
|
||||||
|
* get:
|
||||||
|
* summary: Get all quarterly configurations
|
||||||
|
* tags: [Quarterly Windows Configuration]
|
||||||
|
* security:
|
||||||
|
* - appSignature: []
|
||||||
|
* - bearerAuth: []
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Fetched successfully
|
||||||
|
*/
|
||||||
|
router.get("/quarterly_windows",[verifySignature, verifyToken], quarterlyWindowsController.getAllConfigs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/quarterly_windows/{id}:
|
||||||
|
* get:
|
||||||
|
* summary: Get configuration by ID
|
||||||
|
* tags: [Quarterly Windows Configuration]
|
||||||
|
* security:
|
||||||
|
* - appSignature: []
|
||||||
|
* - bearerAuth: []
|
||||||
|
* parameters:
|
||||||
|
* - name: id
|
||||||
|
* in: path
|
||||||
|
* required: true
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Fetched successfully
|
||||||
|
*/
|
||||||
|
router.get("/quarterly_windows/:id",[verifySignature, verifyToken], quarterlyWindowsController.getConfigById);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @swagger
|
||||||
|
* /api/quarterly_windows/{id}:
|
||||||
|
* put:
|
||||||
|
* summary: Update configuration by ID
|
||||||
|
* tags: [Quarterly Windows Configuration]
|
||||||
|
* security:
|
||||||
|
* - appSignature: []
|
||||||
|
* - bearerAuth: []
|
||||||
|
* parameters:
|
||||||
|
* - name: id
|
||||||
|
* in: path
|
||||||
|
* required: true
|
||||||
|
* requestBody:
|
||||||
|
* required: true
|
||||||
|
* content:
|
||||||
|
* application/json:
|
||||||
|
* schema:
|
||||||
|
* type: object
|
||||||
|
* responses:
|
||||||
|
* 200:
|
||||||
|
* description: Updated successfully
|
||||||
|
*/
|
||||||
|
router.put("/quarterly_windows/:id",[verifySignature, verifyToken], quarterlyWindowsController.updateConfig);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// /**
|
|
||||||
// * @swagger
|
|
||||||
// * /api/notification_templates/{id}:
|
|
||||||
// * delete:
|
|
||||||
// * summary: Delete a notification template
|
|
||||||
// * tags: [Notification Templates]
|
|
||||||
// * parameters:
|
|
||||||
// * - name: id
|
|
||||||
// * in: path
|
|
||||||
// * required: true
|
|
||||||
// * responses:
|
|
||||||
// * 200:
|
|
||||||
// * description: Deleted successfully
|
|
||||||
// */
|
|
||||||
// router.delete("/notification_templates/:id", notificationTemplateController.deleteTemplate);
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
95
app/services/email.service.js
Normal file
95
app/services/email.service.js
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
const nodemailer = require("nodemailer");
|
||||||
|
const { NotificationTemplate } = require("../models"); // adjust path if needed
|
||||||
|
const logger = require("../services/logger");
|
||||||
|
require("dotenv").config();
|
||||||
|
|
||||||
|
const transporter = nodemailer.createTransport({
|
||||||
|
host: process.env.SMTP_HOST,
|
||||||
|
port: process.env.SMTP_PORT,
|
||||||
|
secure: process.env.SMTP_SECURE === "true", // true for 465, false for 587
|
||||||
|
auth: {
|
||||||
|
user: process.env.SMTP_USER,
|
||||||
|
pass: process.env.SMTP_PASS,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace placeholders in template HTML with actual data
|
||||||
|
*/
|
||||||
|
function replacePlaceholders(templateHtml, data) {
|
||||||
|
let html = templateHtml;
|
||||||
|
for (const key in data) {
|
||||||
|
const regex = new RegExp(`{{\\s*${key}\\s*}}`, "g");
|
||||||
|
html = html.replace(regex, data[key]);
|
||||||
|
}
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
exports.sendEmail = async (to, templateCode, data = {}) => {
|
||||||
|
const logPrefix = `[EMAIL SERVICE][${new Date().toISOString()}]`;
|
||||||
|
|
||||||
|
logger.info(`${logPrefix} → Starting email send process`);
|
||||||
|
logger.info(`${logPrefix} → Template Code: ${templateCode}`);
|
||||||
|
logger.info(`${logPrefix} → Recipient: ${to}`);
|
||||||
|
logger.info(`${logPrefix} → Placeholder Data: ${JSON.stringify(data)}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 1. Fetch template from DB
|
||||||
|
const template = await NotificationTemplate.findOne({
|
||||||
|
where: { template_code: templateCode },
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!template) {
|
||||||
|
logger.error(`${logPrefix} ❌ Template not found for code: ${templateCode}`);
|
||||||
|
throw new Error(`Template not found for code: ${templateCode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Replace placeholders
|
||||||
|
const html = replacePlaceholders(template.template_html, data);
|
||||||
|
|
||||||
|
// 3. Prepare mail options
|
||||||
|
const mailOptions = {
|
||||||
|
from: process.env.SMTP_FROM || process.env.SMTP_USER,
|
||||||
|
to,
|
||||||
|
subject: template.subject || "Notification",
|
||||||
|
html,
|
||||||
|
};
|
||||||
|
|
||||||
|
logger.info(`${logPrefix} ✉️ Sending email using SMTP...`);
|
||||||
|
|
||||||
|
// 4. Send mail
|
||||||
|
const info = await transporter.sendMail(mailOptions);
|
||||||
|
|
||||||
|
logger.info(`${logPrefix} ✅ Email sent successfully`);
|
||||||
|
logger.info(`${logPrefix} Message ID: ${info.messageId}`);
|
||||||
|
logger.info(`${logPrefix} Response: ${info.response}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "success",
|
||||||
|
message: "Email sent successfully",
|
||||||
|
messageId: info.messageId,
|
||||||
|
response: info.response,
|
||||||
|
to,
|
||||||
|
subject: template.subject,
|
||||||
|
templateCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`${logPrefix} ❌ Email sending failed`);
|
||||||
|
logger.error(`${logPrefix} Error: ${err.message}`);
|
||||||
|
if (err.stack) logger.error(`${logPrefix} Stack: ${err.stack}`);
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: "failed",
|
||||||
|
message: err.message,
|
||||||
|
templateCode,
|
||||||
|
to,
|
||||||
|
error: err.stack || err.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -1,15 +1,32 @@
|
|||||||
const { createLogger, transports, format } = require('winston');
|
const { createLogger, format, transports } = require("winston");
|
||||||
const path = require('path');
|
const path = require("path");
|
||||||
|
require("winston-daily-rotate-file");
|
||||||
|
|
||||||
|
const logDir = path.join(__dirname, "../writable/logs");
|
||||||
|
|
||||||
|
const dailyRotateFileTransport = new transports.DailyRotateFile({
|
||||||
|
filename: path.join(logDir, "app-%DATE%.log"),
|
||||||
|
datePattern: "YYYY-MM-DD",
|
||||||
|
zippedArchive: false,
|
||||||
|
maxSize: "20m",
|
||||||
|
maxFiles: "30d", // keep logs for 30 days
|
||||||
|
});
|
||||||
|
|
||||||
const logger = createLogger({
|
const logger = createLogger({
|
||||||
|
level: "info",
|
||||||
format: format.combine(
|
format: format.combine(
|
||||||
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
|
||||||
format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)
|
format.printf(
|
||||||
|
(info) => `${info.timestamp} [${info.level.toUpperCase()}]: ${info.message}`
|
||||||
|
)
|
||||||
),
|
),
|
||||||
transports: [
|
transports: [
|
||||||
new transports.File({ filename: path.join(__dirname, '../writable/logs/app.log') }),
|
dailyRotateFileTransport,
|
||||||
new transports.Console()
|
new transports.Console({
|
||||||
]
|
format: format.combine(format.colorize(), format.simple()),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
exitOnError: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = logger;
|
module.exports = logger;
|
||||||
|
|||||||
45
package-lock.json
generated
45
package-lock.json
generated
@ -19,11 +19,13 @@
|
|||||||
"morgan": "^1.10.1",
|
"morgan": "^1.10.1",
|
||||||
"multer": "^2.0.2",
|
"multer": "^2.0.2",
|
||||||
"mysql2": "^3.15.2",
|
"mysql2": "^3.15.2",
|
||||||
|
"nodemailer": "^7.0.10",
|
||||||
"nodemon": "^3.1.10",
|
"nodemon": "^3.1.10",
|
||||||
"sequelize": "^6.37.7",
|
"sequelize": "^6.37.7",
|
||||||
"swagger-jsdoc": "^6.2.8",
|
"swagger-jsdoc": "^6.2.8",
|
||||||
"swagger-ui-express": "^5.0.1",
|
"swagger-ui-express": "^5.0.1",
|
||||||
"winston": "^3.18.3"
|
"winston": "^3.18.3",
|
||||||
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@apidevtools/json-schema-ref-parser": {
|
"node_modules/@apidevtools/json-schema-ref-parser": {
|
||||||
@ -670,6 +672,14 @@
|
|||||||
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
|
"resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz",
|
||||||
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
|
"integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw=="
|
||||||
},
|
},
|
||||||
|
"node_modules/file-stream-rotator": {
|
||||||
|
"version": "0.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/file-stream-rotator/-/file-stream-rotator-0.6.1.tgz",
|
||||||
|
"integrity": "sha512-u+dBid4PvZw17PmDeRcNOtCP9CCK/9lRN2w+r1xIS7yOL9JFrIBKTvrYsxT4P0pGtThYTn++QS5ChHaUov3+zQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"moment": "^2.29.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fill-range": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
@ -1400,6 +1410,14 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/nodemailer": {
|
||||||
|
"version": "7.0.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.10.tgz",
|
||||||
|
"integrity": "sha512-Us/Se1WtT0ylXgNFfyFSx4LElllVLJXQjWi2Xz17xWw7amDKO2MLtFnVp1WACy7GkVGs+oBlRopVNUzlrGSw1w==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/nodemon": {
|
"node_modules/nodemon": {
|
||||||
"version": "3.1.10",
|
"version": "3.1.10",
|
||||||
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
|
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
|
||||||
@ -1443,6 +1461,14 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/object-hash": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
|
||||||
|
"integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-inspect": {
|
"node_modules/object-inspect": {
|
||||||
"version": "1.13.4",
|
"version": "1.13.4",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
|
||||||
@ -2112,6 +2138,23 @@
|
|||||||
"node": ">= 12.0.0"
|
"node": ">= 12.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/winston-daily-rotate-file": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/winston-daily-rotate-file/-/winston-daily-rotate-file-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-JDjiXXkM5qvwY06733vf09I2wnMXpZEhxEVOSPenZMii+g7pcDcTBt2MRugnoi8BwVSuCT2jfRXBUy+n1Zz/Yw==",
|
||||||
|
"dependencies": {
|
||||||
|
"file-stream-rotator": "^0.6.1",
|
||||||
|
"object-hash": "^3.0.0",
|
||||||
|
"triple-beam": "^1.4.1",
|
||||||
|
"winston-transport": "^4.7.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"winston": "^3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/winston-transport": {
|
"node_modules/winston-transport": {
|
||||||
"version": "4.9.0",
|
"version": "4.9.0",
|
||||||
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
|
"resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.9.0.tgz",
|
||||||
|
|||||||
@ -21,10 +21,12 @@
|
|||||||
"morgan": "^1.10.1",
|
"morgan": "^1.10.1",
|
||||||
"multer": "^2.0.2",
|
"multer": "^2.0.2",
|
||||||
"mysql2": "^3.15.2",
|
"mysql2": "^3.15.2",
|
||||||
|
"nodemailer": "^7.0.10",
|
||||||
"nodemon": "^3.1.10",
|
"nodemon": "^3.1.10",
|
||||||
"sequelize": "^6.37.7",
|
"sequelize": "^6.37.7",
|
||||||
"swagger-jsdoc": "^6.2.8",
|
"swagger-jsdoc": "^6.2.8",
|
||||||
"swagger-ui-express": "^5.0.1",
|
"swagger-ui-express": "^5.0.1",
|
||||||
"winston": "^3.18.3"
|
"winston": "^3.18.3",
|
||||||
|
"winston-daily-rotate-file": "^5.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user