diff --git a/dist/app.js b/dist/app.js new file mode 100644 index 0000000..224b50a --- /dev/null +++ b/dist/app.js @@ -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(); + }); +} diff --git a/dist/config/envs.js b/dist/config/envs.js new file mode 100644 index 0000000..1e8e8f7 --- /dev/null +++ b/dist/config/envs.js @@ -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(), +}; diff --git a/dist/services/consumer.service.js b/dist/services/consumer.service.js new file mode 100644 index 0000000..0da8722 --- /dev/null +++ b/dist/services/consumer.service.js @@ -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; diff --git a/dist/services/email.service.js b/dist/services/email.service.js new file mode 100644 index 0000000..05b29ce --- /dev/null +++ b/dist/services/email.service.js @@ -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 = ` + //

${title}

+ //

Por favor haz click en el siguiente link para acceder al chat

+ // ${title} + // `; + 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 = ` + //

${title}

+ //

Por favor haz click en el siguiente link para ver el animal

+ // ${title} + // `; + 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 = ` + //

${title}

+ //

Por favor haz click en el siguiente link para ${action}

+ // ${title} + // `; + 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; diff --git a/dist/services/queue.service.js b/dist/services/queue.service.js new file mode 100644 index 0000000..c0588e0 --- /dev/null +++ b/dist/services/queue.service.js @@ -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; diff --git a/src/templates/chatMessages.js b/dist/templates/chatMessages.js similarity index 94% rename from src/templates/chatMessages.js rename to dist/templates/chatMessages.js index 5751a93..3f0275e 100644 --- a/src/templates/chatMessages.js +++ b/dist/templates/chatMessages.js @@ -1,5 +1,8 @@ -export const chatMessages = link => { - return ` +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.chatMessages = void 0; +const chatMessages = (link) => { + return ` { `; }; +exports.chatMessages = chatMessages; diff --git a/src/templates/mailValidation.js b/dist/templates/mailValidation.js similarity index 94% rename from src/templates/mailValidation.js rename to dist/templates/mailValidation.js index 9f16f17..45059dd 100644 --- a/src/templates/mailValidation.js +++ b/dist/templates/mailValidation.js @@ -1,5 +1,8 @@ -export const mailValidation = (title, action, link) => { - return ` +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.mailValidation = void 0; +const mailValidation = (title, action, link) => { + return `
{ `; }; +exports.mailValidation = mailValidation; diff --git a/src/templates/petChanged.js b/dist/templates/petChanged.js similarity index 95% rename from src/templates/petChanged.js rename to dist/templates/petChanged.js index c2528ba..1710b5a 100644 --- a/src/templates/petChanged.js +++ b/dist/templates/petChanged.js @@ -1,5 +1,8 @@ -export const petChanged = (webServiceUrl, link) => { - return ` +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.petChanged = void 0; +const petChanged = (webServiceUrl, link) => { + return `
{ `; }; +exports.petChanged = petChanged; diff --git a/src/services/email.service.ts b/src/services/email.service.ts index f0b37ab..9914a12 100644 --- a/src/services/email.service.ts +++ b/src/services/email.service.ts @@ -74,7 +74,7 @@ export class EmailService { // ${title} // `; - const html = chatMessages(link) + const html = chatMessages(link); const options = { to: email, @@ -106,7 +106,7 @@ export class EmailService { // ${title} // `; - const html = petChanged(this.webServiceUrl, link) + const html = petChanged(this.webServiceUrl!, link); const options = { to: email, @@ -170,7 +170,7 @@ export class EmailService { //

Por favor haz click en el siguiente link para ${action}

// ${title} // `; - const html = mailValidation(title,action, link) + const html = mailValidation(title, action, link); return { html, title }; } diff --git a/src/templates/chatMessages.ts b/src/templates/chatMessages.ts new file mode 100644 index 0000000..f761da9 --- /dev/null +++ b/src/templates/chatMessages.ts @@ -0,0 +1,125 @@ +export const chatMessages = (link: string) => { + return ` + +
+ + + + + + + + + + + + +
+ + + + + + + + + + +
+

Tienes mensajes de chat sin leer +

+ +

+ Por favor haz click en el siguiente link para acceder al chat +

+
+ Ver Mensajes +
+
+ + + + `; +}; diff --git a/src/templates/mailValidation.ts b/src/templates/mailValidation.ts new file mode 100644 index 0000000..04e40d6 --- /dev/null +++ b/src/templates/mailValidation.ts @@ -0,0 +1,125 @@ +export const mailValidation = (title: string, action: string, link: string) => { + return ` + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+

${title}

+ + +

+ Por favor haz click en el siguiente link para ${action} +

+
+ ${title} +
+
+ + + + `; +}; diff --git a/src/templates/petChanged.ts b/src/templates/petChanged.ts new file mode 100644 index 0000000..f31528b --- /dev/null +++ b/src/templates/petChanged.ts @@ -0,0 +1,141 @@ +export const petChanged = (webServiceUrl: string, link: string) => { + return ` + + + + + + + + + + + + + + +
+ + + + + + + + + + +
+

+ Un peludo de tus favoritos +

+

+ ¡Ha cambiado! +

+

+ Pulsa en el link para ver el peludo +

+
+ Ver ficha del peludo +
+
+ + + + `; +};