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

[OCRVS-6410] Mass email users #937

Merged
merged 13 commits into from
Apr 9, 2024
Merged
1 change: 1 addition & 0 deletions infrastructure/deployment/deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ export METRICS_MONGODB_PASSWORD=`generate_password`
export PERFORMANCE_MONGODB_PASSWORD=`generate_password`
export OPENHIM_MONGODB_PASSWORD=`generate_password`
export WEBHOOKS_MONGODB_PASSWORD=`generate_password`
export NOTIFICATION_MONGODB_PASSWORD=`generate_password`

#
# Elasticsearch credentials
Expand Down
6 changes: 5 additions & 1 deletion infrastructure/docker-compose.deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ services:
- METRICS_MONGODB_PASSWORD=${METRICS_MONGODB_PASSWORD}
- OPENHIM_MONGODB_PASSWORD=${OPENHIM_MONGODB_PASSWORD}
- WEBHOOKS_MONGODB_PASSWORD=${WEBHOOKS_MONGODB_PASSWORD}
- NOTIFICATION_MONGODB_PASSWORD=${NOTIFICATION_MONGODB_PASSWORD}
networks:
- overlay_net
logging:
Expand Down Expand Up @@ -554,7 +555,7 @@ services:
deploy:
labels:
- 'traefik.enable=true'
- 'traefik.http.routers.countryconfig.rule=Host(`countryconfig.{{hostname}}`) && !Path(`/email`)'
- 'traefik.http.routers.countryconfig.rule=Host(`countryconfig.{{hostname}}`) && !Path(`/email`) && !Path(`/notification`)'
- 'traefik.http.services.countryconfig.loadbalancer.server.port=3040'
- 'traefik.http.routers.countryconfig.tls=true'
- 'traefik.http.routers.countryconfig.tls.certresolver=certResolver'
Expand All @@ -572,6 +573,8 @@ services:
- 'traefik.http.middlewares.block-email.ipwhitelist.sourcerange=255.255.255.255'
- 'traefik.http.routers.block-email.rule=Host(`countryconfig.{{hostname}}`) && Path(`/email`)'
- 'traefik.http.routers.block-email.middlewares=block-email'
- 'traefik.http.routers.block-notification.rule=Host(`countryconfig.{{hostname}}`) && Path(`/notification`)'
- 'traefik.http.routers.block-notification.middlewares=block-email'
replicas: 1
environment:
- MONGO_URL=mongodb://mongo1/user-mgnt?replicaSet=rs0
Expand Down Expand Up @@ -687,6 +690,7 @@ services:
environment:
- APN_SERVICE_URL=http://apm-server:8200
- CERT_PUBLIC_KEY_PATH=/run/secrets/jwt-public-key.{{ts}}
- MONGO_URL=mongodb://notification:${NOTIFICATION_MONGODB_PASSWORD}@mongo1/notification?replicaSet=rs0
deploy:
replicas: 1
labels:
Expand Down
1 change: 1 addition & 0 deletions infrastructure/docker-compose.production-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ services:
- NODE_ENV=production
- LANGUAGES=en,fr
- SENTRY_DSN=${SENTRY_DSN:-""}
- MONGO_URL=mongodb://notification:${NOTIFICATION_MONGODB_PASSWORD}@mongo1,mongo2/notification?replicaSet=rs0
deploy:
replicas: 2

Expand Down
1 change: 1 addition & 0 deletions infrastructure/docker-compose.staging-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ services:
- NODE_ENV=production
- LANGUAGES=en,fr
- SENTRY_DSN=${SENTRY_DSN:-""}
- MONGO_URL=mongodb://notification:${NOTIFICATION_MONGODB_PASSWORD}@mongo1/notification?replicaSet=rs0
deploy:
replicas: 1

Expand Down
22 changes: 22 additions & 0 deletions infrastructure/mongodb/on-deploy.sh
Original file line number Diff line number Diff line change
Expand Up @@ -231,3 +231,25 @@ else
})
EOF
fi

NOTIFICATION_USER=$(echo $(checkIfUserExists "notification"))
if [[ $NOTIFICATION_USER != "FOUND" ]]; then
echo "notification user not found"
mongo $(mongo_credentials) --host $HOST <<EOF
use notification
db.createUser({
user: 'notification',
pwd: '$NOTIFICATION_MONGODB_PASSWORD',
roles: [{ role: 'readWrite', db: 'notification' }]
})
EOF
else
echo "notification user exists"
mongo $(mongo_credentials) --host $HOST <<EOF
use notification
db.updateUser('notification', {
pwd: '$NOTIFICATION_MONGODB_PASSWORD',
roles: [{ role: 'readWrite', db: 'notification' }]
})
EOF
fi
16 changes: 12 additions & 4 deletions src/api/notification/email-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const sendEmail = async (params: {
html: string
from: string
to: string
bcc?: string[]
}) => {
const replaceVariables = (text: string) =>
Handlebars.compile(text)({
Expand Down Expand Up @@ -65,13 +66,20 @@ export const sendEmail = async (params: {
pass: SMTP_PASSWORD
}
})
const mailOptions = params.bcc
? { ...formattedParams, bcc: params.bcc }
: formattedParams

try {
await emailTransport.sendMail(formattedParams)
await emailTransport.sendMail(mailOptions)
} catch (error) {
logger.error(
`Unable to send email to ${formattedParams.to} for error : ${error}`
)
if (params.bcc) {
logger.error(`Unable to send mass email for error : ${error}`)
} else {
logger.error(
`Unable to send email to ${formattedParams.to} for error : ${error}`
)
}

if (error.response) {
logger.error(error.response.body)
Expand Down
12 changes: 12 additions & 0 deletions src/api/notification/email-templates/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ type RejectionDeclarationVariables = DeclarationCommonVariables & {
name: string
}

type AllUserNotificationVariables = {
subject: string
body: string
}

const templates = {
'onboarding-invite': {
type: 'onboarding-invite',
Expand Down Expand Up @@ -223,6 +228,13 @@ const templates = {
type: 'deathRejectionNotification',
subject: 'Death declaration required update',
template: readDeathTemplate<RejectionDeclarationVariables>('rejection')
},
allUserNotification: {
type: 'allUserNotification',
subject: '', // Subject defined from National Sys Admin Dashboard
template: readOtherTemplate<AllUserNotificationVariables>(
'all-user-notification'
)
}
} as const

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<!DOCTYPE html>
<html>

<head>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;700&display=swap" rel="stylesheet" />
<style type="text/css">
body {
font-family: 'Noto Sans', sans-serif;
color: #222;
padding: 16px 24px;
}

h1 {
font-size: 21px;
margin-top: 32px;
margin-bottom: 36px;
font-weight: 700;
}

p {
font-weight: 400;
font-size: 16px;
line-height: 1.8;
margin-bottom: 24px;
}

i {
color: #666;
font-weight: 400;
font-size: 16px;
}
</style>
</head>

<body>
<img src="{{countryLogo}}" alt="country_logo" style="max-height: 88px;
max-width: 100%;">
<h1>{{subject}}</h1>

<p>
{{body}}
</p>
</br>
<p>
Best regards,
<br />
Farajaland CRVS Team
</p>
</br>
<i>This is an automated message. Please do not reply to this email.</i>
</body>

</html>
17 changes: 10 additions & 7 deletions src/api/notification/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type EmailNotificationPayload = {
}
recipient: {
email: string
bcc?: string[]
}
type: 'user' | 'informant'
locale: string
Expand All @@ -64,13 +65,14 @@ type NotificationPayload = SMSNotificationPayload | EmailNotificationPayload

export const notificationSchema = Joi.object({
templateName: Joi.object({
email: Joi.string().required(),
sms: Joi.string().required()
}),
email: Joi.string(),
sms: Joi.string()
}).xor('email', 'sms'),
recipient: Joi.object({
email: Joi.string().allow(null, '').optional(),
sms: Joi.string().allow(null, '').optional()
}),
email: Joi.string(),
sms: Joi.string(),
bcc: Joi.array().items(Joi.string().required()).optional()
}).xor('email', 'sms'),
type: Joi.string().valid('user', 'informant').required()
}).unknown(true)

Expand Down Expand Up @@ -116,7 +118,8 @@ export async function notificationHandler(
subject: emailSubject,
html: emailBody,
from: SENDER_EMAIL_ADDRESS,
to: recipient.email
to: recipient.email,
bcc: recipient.bcc
})
} else {
const { templateName, variables, recipient, locale } = payload
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,7 @@ export async function createServer() {
handler: notificationHandler,
options: {
tags: ['api'],
auth: false,
validate: {
payload: notificationSchema
},
Expand Down
9 changes: 9 additions & 0 deletions src/translations/client.csv
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,10 @@ config.deathDefaultTempDesc,Label for default death certificate template,Default
config.deathTemplate,Label for death certificate template,Death certificate,Acte de mariage
config.deathUpdatedTempDesc,,Updated {deathLongDate},Mise à jour de {deathLongDate}
config.downloadTemplate,Download action in certificate config action menu,Download,Télécharger
config.emailAllUsers.modal.supportingCopy,Label for send email all users confirmation supporting copy,User will receive emails over the next 24 hours,L'utilisateur recevra des courriels au cours des prochaines 24 heures
config.emailAllUsers.modal.title,Label for send email all users confirmation title,Send email to all users?,Envoyer un e-mail à tous les utilisateurs ?
config.emailAllUsers.subtitle,Subtitle for email all users,This email will be sent to all users you are active. Emails will be sent over the next 24 hours. Only one email can be sent per day,Cet e-mail sera envoyé à tous les utilisateurs que vous activez. Les courriels seront envoyés au cours des prochaines 24 heures. Un seul courriel peut être envoyé par jour
config.emailAllUsers.title,Title for email all users,Email all users,Envoyer un e-mail à tous les utilisateurs
config.eventUpdatedTempDesc,Label for updated birth certificate template,"Updated {lastModified, date, ::dd MMMM yyyy}","Mis à jour {lastModified, date, ::dd MMMM yyyy}"
config.form.settings.time,,Time input,Saisie de l'heure
config.form.tools.input.customSelectWithDynamicOptions,,Custom select with dynamic options,Sélection personnalisée avec options dynamiques
Expand Down Expand Up @@ -344,6 +348,8 @@ constants.downloading,Label for declaration download status Downloading,Download
constants.draft,A label for draft,Draft,Brouillon
constants.duplicateOf,table header for `duplicate of` in record audit,Duplicate of,Duplicata de
constants.emailAddress,Email label,Email Address,Adresse e-mail
constants.emailBody,Label for email body input,Message,Message
constants.emailSubject,Label for email subject input,Subject,Sujet
constants.entrepeneur,The description for ENTREPENEUR type,Entrepeneur,Entrepeneur
constants.estimatedNumberOfEvents,A label for Estimated number of events,"Estimated{lineBreak}no. of {eventType, select, birth {birth} death {death} other {birth}}s","Estimation{lineBreak}no. de {eventType, select, naissance {birth} décès {death} autre {birth}}s"
constants.estimatedNumberOfRegistartion,A label for estimated no. of registrations,Estimated no. of registrations,Nombre estimé déclaration
Expand Down Expand Up @@ -1457,6 +1463,8 @@ misc.nidCallback.failedToAuthenticateNid,Label for nid authention failed phase,F
misc.notif.declarationsSynced,The message that appears in notification when background sync takes place,"As you have connectivity, we can synchronize your declarations.","Comme vous disposez d'une connectivité, nous pouvons synchroniser vos déclarations."
misc.notif.draftsSaved,The message that appears in notification when save drafts button is clicked,Your draft has been saved,Votre brouillon a été enregistré
misc.notif.duplicateRecord,Label for when a duplicate record is detected when registering a record.,{trackingId} is a potential duplicate. Record is ready for review.,{trackingId} est un doublon potentiel. L'enregistrement est prêt à être examiné.
misc.notif.emailAllUsersError,Label for Email all users error toast,Only one email can be sent per day,Un seul e-mail peut être envoyé par jour
misc.notif.emailAllUsersSuccess,Label for Email all users success toast,Email sent to all users,Email envoyé à tous les utilisateurs
misc.notif.offlineError,The message that appears in notification when a new user creation fails in offline mode,Offline. Try again when reconnected,Hors ligne. Réessayez une fois reconnecté
misc.notif.onlineUserStatus,Label for online user status toast notification,You are back online,Vous êtes de nouveau en ligne
misc.notif.outboxText,Declaration outbox text,Outbox ({num}),Boîte d'envoi({num})
Expand All @@ -1483,6 +1491,7 @@ navigation.completenessRates,Completeness rates in navigation,Completeness rates
navigation.config,Config label in navigation,Configuration,Paramétrages
navigation.dashboard,Dashboard Section,Dashboard,Tableau de bord
navigation.declarationForms,Declaration forms label in navigation,Declaration forms,Formulaires de déclaration
navigation.emailAllUsers,Email all users label in navigation,Email all users,Envoyer un e-mail à tous les utilisateurs
navigation.informantNotification,Informant notifications label in navigation,Informant notifications,Notifications des informateurs
navigation.integration,Integration forms label in navigation,Integrations,Intégrations
navigation.leaderboards,Leaderboards Dashboard Section,Leaderboards,Classements
Expand Down
Loading