Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Email template #1

Merged
merged 5 commits into from
Apr 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions dist/app.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
require("dotenv/config");
const email_service_1 = require("./services/email.service");
const envs_1 = require("./config/envs");
const consumer_service_1 = require("./services/consumer.service");
const queue_service_1 = require("./services/queue.service");
const VERIFY_QUEUE = 'verify-email';
const PASSWORD_QUEUE = 'change-password';
const NOTIFICATION_QUEUE = 'animal-changed-notification';
const CHAT_MESSAGE_QUEUE = 'chat-message';
(() => __awaiter(void 0, void 0, void 0, function* () {
yield main();
}))();
function main() {
return __awaiter(this, void 0, void 0, function* () {
const errorLogsService = new queue_service_1.QueueService(envs_1.envs.RABBITMQ_URL, 'error-notification');
const emailService = new email_service_1.EmailService({
mailerService: envs_1.envs.MAIL_SERVICE,
mailerEmail: envs_1.envs.MAILER_EMAIL,
senderEmailPassword: envs_1.envs.MAILER_SECRET_KEY,
}, errorLogsService, envs_1.envs.WEBSERVICE_URL);
const verifyEmailConsumer = new consumer_service_1.ConsumerService(emailService, errorLogsService, envs_1.envs.RABBITMQ_URL, VERIFY_QUEUE);
yield verifyEmailConsumer.consume();
const animalChangedNotificationConsumer = new consumer_service_1.ConsumerService(emailService, errorLogsService, envs_1.envs.RABBITMQ_URL, NOTIFICATION_QUEUE);
yield animalChangedNotificationConsumer.consume();
const changePasswordConsumer = new consumer_service_1.ConsumerService(emailService, errorLogsService, envs_1.envs.RABBITMQ_URL, PASSWORD_QUEUE);
yield changePasswordConsumer.consume();
const unreadMessagesConsumer = new consumer_service_1.ConsumerService(emailService, errorLogsService, envs_1.envs.RABBITMQ_URL, CHAT_MESSAGE_QUEUE);
yield unreadMessagesConsumer.consume();
});
}
17 changes: 17 additions & 0 deletions dist/config/envs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.envs = void 0;
require("dotenv/config");
const env_var_1 = require("env-var");
exports.envs = {
//* Email Service
MAIL_SERVICE: (0, env_var_1.get)('MAIL_SERVICE').required().asString(),
MAILER_EMAIL: (0, env_var_1.get)('MAILER_EMAIL').required().asString(),
MAILER_SECRET_KEY: (0, env_var_1.get)('MAILER_SECRET_KEY').required().asString(),
//* FRONTEND URL
WEBSERVICE_URL: (0, env_var_1.get)('WEBSERVICE_URL').required().asString(),
//* RABBITMQ
RABBITMQ_USER: (0, env_var_1.get)('RABBITMQ_USER').required().asString(),
RABBITMQ_PASS: (0, env_var_1.get)('RABBITMQ_PASS').required().asString(),
RABBITMQ_URL: (0, env_var_1.get)('RABBITMQ_URL').required().asString(),
};
89 changes: 89 additions & 0 deletions dist/services/consumer.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConsumerService = void 0;
const amqp_connection_manager_1 = __importDefault(require("amqp-connection-manager"));
/**
* Creates an instance of ConsumerService.
* @param emailService The email service used to send emails.
* @param errorLogsService The queue service used to log error messages.
* @param rabbitmqUrl The URL of the RabbitMQ server.
* @param queue The name of the queue to consume messages from.
*/
class ConsumerService {
constructor(emailService, errorLogsService, rabbitmqUrl, queue) {
this.emailService = emailService;
this.errorLogsService = errorLogsService;
this.rabbitmqUrl = rabbitmqUrl;
this.queue = queue;
this.channelWrapper = undefined;
this.EXCHANGE = 'email-request';
try {
const connection = amqp_connection_manager_1.default.connect(this.rabbitmqUrl);
this.channelWrapper = connection.createChannel();
}
catch (error) {
console.log(error);
}
}
/**
* Starts consuming messages from the specified queue.
*/
consume() {
return __awaiter(this, void 0, void 0, function* () {
try {
yield this.channelWrapper.addSetup((channel) => __awaiter(this, void 0, void 0, function* () {
yield channel.assertQueue(this.queue, { durable: true });
yield channel.bindQueue(this.queue, this.EXCHANGE, this.queue);
yield channel.consume(this.queue, (message) => __awaiter(this, void 0, void 0, function* () {
if (message) {
const content = JSON.parse(message.content.toString());
switch (this.queue) {
case 'animal-changed-notification': {
yield this.emailService.sendAnimalChangedNotification(content);
break;
}
case 'verify-email': {
yield this.emailService.sendEmailValidationLink(content);
break;
}
case 'change-password': {
yield this.emailService.sendEmailValidationLink(content);
break;
}
case 'chat-message': {
yield this.emailService.sendUnreadChatMessage(content);
}
default: {
('Unknown queue');
}
}
channel.ack(message);
}
}));
}));
console.log(`${this.queue} consumer service started and listening for messages`);
}
catch (err) {
console.log('Error starting the consumer: ', err);
this.errorLogsService.addMessageToQueue({
message: `Error starting the ${this.queue} consumer: ${err}`,
level: 'high',
origin: 'WebSocket Server',
}, 'error-logs');
}
});
}
}
exports.ConsumerService = ConsumerService;
158 changes: 158 additions & 0 deletions dist/services/email.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EmailService = void 0;
const nodemailer_1 = __importDefault(require("nodemailer"));
const petChanged_1 = require("../templates/petChanged");
const chatMessages_1 = require("../templates/chatMessages");
const mailValidation_1 = require("../templates/mailValidation");
/**
* Service for sending emails.
*/
class EmailService {
constructor({ mailerService, mailerEmail, senderEmailPassword }, errorLogsService, webServiceUrl) {
this.errorLogsService = errorLogsService;
this.webServiceUrl = webServiceUrl;
this.transporter = nodemailer_1.default.createTransport({
service: mailerService,
auth: {
user: mailerEmail,
pass: senderEmailPassword,
},
});
}
/**
* Sends an email notification for unread chat messages.
* @param options Options for sending unread chat message notification.
* @returns A boolean indicating whether the email was sent successfully.
*/
sendUnreadChatMessage({ chat, email }) {
return __awaiter(this, void 0, void 0, function* () {
const title = 'Tienes mensajes de chat sin leer';
const endPoint = 'private/chat';
const link = `${this.webServiceUrl}/${endPoint}/${chat}`;
// const html = `
// <h1>${title}</h1>
// <p>Por favor haz click en el siguiente link para acceder al chat</p>
// <a href="${link}">${title}</a>
// `;
const html = (0, chatMessages_1.chatMessages)(link);
const options = {
to: email,
subject: title,
htmlBody: html,
};
const isSent = yield this.sendEmail(options);
if (!isSent)
return false;
return true;
});
}
/**
* Sends a notification when an animal of interest changes.
* @param options Options for sending animal changed notification.
* @returns A boolean indicating whether the email was sent successfully.
*/
sendAnimalChangedNotification({ link, email, }) {
return __awaiter(this, void 0, void 0, function* () {
const title = 'Un animal de tus favoritos ha cambiado!';
// const html = `
// <h1>${title}</h1>
// <p>Por favor haz click en el siguiente link para ver el animal</p>
// <a href=${this.webServiceUrl}/${link}>${title}</a>
// `;
const html = (0, petChanged_1.petChanged)(this.webServiceUrl, link);
const options = {
to: email,
subject: title,
htmlBody: html,
};
const isSent = yield this.sendEmail(options);
if (!isSent)
return false;
return true;
});
}
/**
* Sends an email validation link.
* @param options Options for sending email validation link.
* @returns A boolean indicating whether the email was sent successfully.
*/
sendEmailValidationLink({ email, verificationToken, type, }) {
return __awaiter(this, void 0, void 0, function* () {
const { html, title } = this.generateEmailContent(type, verificationToken, email);
const options = {
to: email,
subject: title,
htmlBody: html,
};
const isSent = yield this.sendEmail(options);
if (!isSent)
return false;
return true;
});
}
/**
* Generates email content based on the type of email and token provided.
* @param type The type of email (either 'email' or 'reset-password').
* @param token The token to include in the email link.
* @param email The recipient's email address.
* @returns An object containing the HTML content of the email and its title.
*/
generateEmailContent(type, token, email) {
const endPoint = type === 'email' ? 'verify-email' : 'reset-password';
const title = type === 'email' ? 'Valida tu Email' : 'Cambia tu password';
const action = type === 'email' ? 'validar tu email' : 'cambiar tu password';
const link = `${this.webServiceUrl}/${endPoint}/${token}`;
// const html = `
// <h1>${title}</h1>
// <p>Por favor haz click en el siguiente link para ${action}</p>
// <a href="${link}">${title}</a>
// `;
const html = (0, mailValidation_1.mailValidation)(title, action, link);
return { html, title };
}
/**
* Sends an email based on the provided options.
* @param options The options for sending the email.
* @returns A boolean indicating whether the email was sent successfully.
*/
sendEmail(options) {
return __awaiter(this, void 0, void 0, function* () {
const { to, subject, htmlBody, attachments: attachments = [] } = options;
if ((typeof to === 'string' && to === 'test@test.com') ||
to.includes('test@test.com'))
return false;
try {
const sentInformation = yield this.transporter.sendMail({
to: to,
subject: subject,
html: htmlBody,
attachments: attachments,
});
return true;
}
catch (error) {
console.log({ error });
this.errorLogsService.addMessageToQueue({
message: `Error sending email: ${error}`,
level: 'high',
origin: 'Email Service',
}, 'error-logs');
return false;
}
});
}
}
exports.EmailService = EmailService;
60 changes: 60 additions & 0 deletions dist/services/queue.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.QueueService = void 0;
const amqp_connection_manager_1 = __importDefault(require("amqp-connection-manager"));
/**
* ProducerService class for sending messages to RabbitMQ queues.
*/
class QueueService {
/**
* Constructs an instance of ProducerService.
* @param rabbitmqUrl - URL of the RabbitMQ server.
* @param exchange - Name of the exchange to publish messages.
*/
constructor(rabbitmqUrl, exchange) {
this.rabbitmqUrl = rabbitmqUrl;
this.exchange = exchange;
// Establishes connection to RabbitMQ server and creates a channel wrapper.
const connection = amqp_connection_manager_1.default.connect(this.rabbitmqUrl);
this.channelWrapper = connection.createChannel({
// Ensures the exchange is declared upon channel creation.
setup: (channel) => {
return channel.assertExchange(this.exchange, 'direct', {
durable: true,
});
},
});
console.log(`${this.exchange} exchange created`);
}
/**
* Adds a message to the specified queue in the exchange.
* @param payload - Data to be sent in the message.
* @param queue - Name of the queue to send the message.
*/
addMessageToQueue(payload, queue) {
return __awaiter(this, void 0, void 0, function* () {
try {
console.log({ payload, queue });
// Publishes the message to the specified queue in the exchange.
yield this.channelWrapper.publish('email-request', queue, Buffer.from(JSON.stringify(payload)), { persistent: true });
console.log('Message sent to queue');
}
catch (error) {
console.log('Error sending message to queue', error);
}
});
}
}
exports.QueueService = QueueService;
Loading
Loading