Skip to content
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
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:16
FROM node:20

#Set working directory
WORKDIR /var/src/
Expand Down
13 changes: 10 additions & 3 deletions deployment/ansible.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,13 @@
- name: Update npm
shell: cd {{release_path}}/src && npm i

- name: Delete Old Folder & create folder
shell: rm -rf {{ current_path }} && cd {{ project_path }} && mkdir notification
- name: Delete Old Folder
file:
path: "{{ current_path }}"
state: absent

- name: create folder
shell: cd {{ project_path }} && mkdir notification

- name: Move code from release to service folder
shell: mv "{{ release_path }}"/* {{ current_path }}/
Expand All @@ -63,7 +68,9 @@
ignore_errors: yes

- name: Delete release folder
shell: rm -rf {{ release_path }}
file:
path: "{{ release_path }}"
state: absent

- name: Start pm2
command: "chdir={{current_path}}/src pm2 start app.js -i 2 --name elevate-notification"
Expand Down
22 changes: 14 additions & 8 deletions src/api-doc/MentorED-Notification.postman_collection.json
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
{
"info": {
"_postman_id": "c208eafb-8d59-4414-8b02-41bde6580ca9",
"_postman_id": "fa88f778-f705-47c6-8d27-7b5f068ced12",
"name": "MentorED-Notification",
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
"_exporter_id": "7997930"
"_exporter_id": "21498549"
},
"item": [
{
"name": "Send Email",
"name": "SendEmail",
"request": {
"method": "POST",
"header": [
{
"key": "internal_access_token",
"value": "bsj82AHBxahusub12yexlashsbxAXADHBlaj",
"value": "{{internal_access_token}}",
"type": "text"
}
],
"body": {
"mode": "raw",
"raw": "{\n \"type\":\"email\",\n \"email\":{\n \"to\": \"ankitstar00786@gmail.com\",\n\t\t\"subject\": \"MentorED - Reset Otp\",\n\t \"body\": \"<p>Dear Ankit,</p> Your OTP to reset your password is <strong>123456</strong>. Please enter the OTP to reset your password. For your security, please do not share this OTP with anyone.\"\n }\n}",
"raw": "{\n \"type\": \"email\",\n \"email\": {\n \"to\": \"nevil@tunerlabs.com\",\n \"subject\": \"Testing email logs\",\n \"body\": \"Sample Data\",\n \"attachments\": [\n {\n \"url\": \"https://www.clickdimensions.com/links/TestPDFfile.pdf\",\n \"filename\": \"some-pdf.pdf\",\n \"type\": \"application/pdf\",\n \"disposition\": \"attachment\",\n \"content_id\": \"mytext\"\n },\n {\n \"url\": \"https://sample-videos.com/csv/Sample-Spreadsheet-10-rows.csv\",\n \"filename\": \"some-csv.csv\",\n \"type\": \"application/csv\",\n \"disposition\": \"attachment\",\n \"content_id\": \"mytext\"\n }\n ]\n }\n}",
"options": {
"raw": {
"language": "json"
Expand All @@ -28,11 +28,17 @@
},
"url": {
"raw": "{{notificationBaseUrl}}notification/v1/email/send",
"host": ["{{notificationBaseUrl}}notification"],
"path": ["v1", "email", "send"]
"host": [
"{{notificationBaseUrl}}notification"
],
"path": [
"v1",
"email",
"send"
]
}
},
"response": []
}
]
}
}
43 changes: 31 additions & 12 deletions src/api-doc/api-doc.yaml
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
---
openapi: 3.0.0
info:
title: Elevate Notification
version: 1.0.0
termsOfService: 'https://github.com/project-sunbird/sunbird-commons/blob/master/LICENSE'
termsOfService: https://github.com/project-sunbird/sunbird-commons/blob/master/LICENSE
description: >-
- The Notification Service is a centralized Service to support other services. Apis perform operations related to sending email notification etc
- The Notification Service is a centralized Service to support other
services. Apis perform operations related to sending email notification etc

- The URL for Users API(s) is `{context}/notification/v1`
- <b>Note:</b> These resources can be used in other services
- The URL for Users API(s) is `{context}/notification/v1` - <b>Note:</b>
These resources can be used in other services
contact:
email: tech-infra@shikshalokam.org
servers:
- url: http://localhost:3002
description: local server url
- url: https://dev.elevate-apis.shikshalokam.org
description: dev server url

paths:
'/notification/v1/email/send':
/notification/v1/email/send:
post:
summary: Send Email
tags:
Expand All @@ -28,7 +27,6 @@ paths:
- Endpoint for sending email `/notification/v1/email/send`
- It is mandatory to provide values for parameters marked as `required`.
- Mandatory parameters cannot be empty or null.

parameters:
- name: internal_access_token
in: header
Expand All @@ -41,20 +39,39 @@ paths:
content:
application.json:
schema:
'$ref': '#/components/schemas/email/emailSendRequest'
$ref: '#/components/schemas/email/emailSendRequest'
examples:
example1:
value:
type: email
email:
to: example@mail.com
cc: ccexample@mail.com
subject: Subject of email
body: |-
Dear Jhon,
Welcome to Notification
attachments:
- url: https://www.clickdimensions.com/links/TestPDFfile.pdf
filename: some-pdf.pdf
type: application/pdf
- url: >-
https://sample-videos.com/csv/Sample-Spreadsheet-10-rows.csv
filename: some-csv.csv
type: application/csv
responses:
'200':
description: OK. Email sent successfully.
content:
application.json:
schema:
'$ref': '#/components/schemas/email/emailSendResponse200'
$ref: '#/components/schemas/email/emailSendResponse200'
'400':
description: Bad Request.
content:
application.json:
schema:
'$ref': '#/components/schemas/email/emailSendResponse400'
$ref: '#/components/schemas/email/emailSendResponse400'
components:
schemas:
email:
Expand Down Expand Up @@ -94,7 +111,9 @@ components:
body:
type: string
description: Body of Email. It will accept Html too.
example: "Dear Jhon, \n Welcome to Notification"
example: |-
Dear Jhon,
Welcome to Notification
required: true
emailSendResponse200:
description: Email Sent response
Expand Down
62 changes: 60 additions & 2 deletions src/generics/helpers/email-notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,35 @@
const sgMail = require('@sendgrid/mail')
sgMail.setApiKey(process.env.SENDGRID_API_KEY)
const logQueries = require('../../database/queries/log')
const request = require('request')

/**
* Fetches a file from a given URL.
* @param {Object} fileUrl - The URL object containing information about the file.
* @param {string} fileUrl.url - The URL of the file to fetch.
* @param {string} fileUrl.filename - The name of the file.
* @returns {Promise<Object>} A promise that resolves with an object containing the file content and filename.
* @throws {Error} If an error occurs during the file fetch operation, or if the response status code is 400 or greater than or equal to 500.
*/
async function fetchFileByUrl(fileUrl) {
try {
const response = await new Promise((resolve, reject) => {
request(fileUrl.url, { encoding: null }, (err, res, body) => {
if (err) {
reject(err)
} else if (res.statusCode === 400 || res.statusCode >= 500) {
// Handle 400 Bad Request and server errors
reject(new Error(`Request failed with status code ${res.statusCode}`))
} else {
resolve({ content: body, filename: fileUrl.filename })
}
})
})
return response
} catch (error) {
throw new Error('Error fetching file: ' + error.message)
}
}

/**
* Send Email
Expand All @@ -24,7 +53,32 @@ const logQueries = require('../../database/queries/log')
*/
async function sendEmail(params) {
try {
let attachments = []
let errorMeta = {}
try {
if (params.attachments && params.attachments.length > 0) {
const processAttachment = async (attachment) => {
const attachmentContent = await fetchFileByUrl(attachment)
return {
content: Buffer.from(attachmentContent.content).toString('base64'),
filename: attachment.filename,
type: attachment.type,
}
}

if (params.attachments.length === 1) {
attachments.push(await processAttachment(params.attachments[0]))
} else {
attachments = await Promise.all(params.attachments.map(processAttachment))
}
}
} catch (error) {
errorMeta = {
attachments: { message: error.message },
}
}
let fromMail = process.env.SENDGRID_FROM_MAIL

if (params.from) {
fromMail = params.from
}
Expand All @@ -35,6 +89,7 @@ async function sendEmail(params) {
to: to, // list of receivers
subject: params.subject, // Subject line
html: params.body,
attachments: attachments,
}
if (params.cc) {
message['cc'] = params.cc.split(',')
Expand All @@ -44,17 +99,19 @@ async function sendEmail(params) {
}
try {
const res = await sgMail.send(message)
const errorResponse = {
errorResponse = {
email: to,
response_code: Number(res[0].statusCode),
meta: errorMeta,
}
await logQueries.createLog(errorResponse)
} catch (error) {
const errorResponse = {
errorResponse = {
email: to,
response_code: Number(error?.code),
error: error?.response,
status: 'FAILED',
meta: errorMeta,
}
await logQueries.createLog(errorResponse)
if (error.response) {
Expand All @@ -66,6 +123,7 @@ async function sendEmail(params) {
message: 'successfully mail sent',
}
} catch (error) {
console.log(error)
return {
status: 'failed',
message: 'Mail server is down, please try after some time',
Expand Down
21 changes: 20 additions & 1 deletion src/validators/v1/email.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,28 @@ module.exports = {

req.checkBody('email.to')
.notEmpty()
.withMessage('email field is empty')
.withMessage('email.to field is empty')
.custom((emailIds) => emailValidation(emailIds))
.withMessage('invalid email ids')
req.checkBody('email.attachments').optional().notEmpty().withMessage('email.attachments field is empty')
if (emailValidation.attachments) {
req.checkBody('email.attachments.*.url')
.notEmpty()
.withMessage('attachments.url field is empty')
.isURL()
.withMessage('attachments.url is invalid')

req.checkBody('email.attachments.*.filename')
.notEmpty()
.withMessage('attachments.filename field is empty')
.isAlphanumeric('en-US', { ignore: '-_' })
.withMessage('attachments.filename is invalid')
req.checkBody('email.attachments.*.filename')
.notEmpty()
.withMessage('attachments.type field is empty')
.isAlphanumeric('en-US', { ignore: '/' })
.withMessage('attachments.type is invalid')
}
},
}

Expand Down