diff --git a/infrastructure/deployment/deploy.sh b/infrastructure/deployment/deploy.sh index d87e18192..0838d8c8f 100755 --- a/infrastructure/deployment/deploy.sh +++ b/infrastructure/deployment/deploy.sh @@ -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 diff --git a/infrastructure/docker-compose.deploy.yml b/infrastructure/docker-compose.deploy.yml index 0b1ef8946..31766fe2e 100644 --- a/infrastructure/docker-compose.deploy.yml +++ b/infrastructure/docker-compose.deploy.yml @@ -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: @@ -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' @@ -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 @@ -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: diff --git a/infrastructure/docker-compose.production-deploy.yml b/infrastructure/docker-compose.production-deploy.yml index 6485386c1..f00dbe32b 100644 --- a/infrastructure/docker-compose.production-deploy.yml +++ b/infrastructure/docker-compose.production-deploy.yml @@ -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 diff --git a/infrastructure/docker-compose.staging-deploy.yml b/infrastructure/docker-compose.staging-deploy.yml index 081f0bca8..783e2a598 100644 --- a/infrastructure/docker-compose.staging-deploy.yml +++ b/infrastructure/docker-compose.staging-deploy.yml @@ -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 diff --git a/infrastructure/mongodb/on-deploy.sh b/infrastructure/mongodb/on-deploy.sh index 999ab279a..fc10bc854 100755 --- a/infrastructure/mongodb/on-deploy.sh +++ b/infrastructure/mongodb/on-deploy.sh @@ -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 < { const replaceVariables = (text: string) => Handlebars.compile(text)({ @@ -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) diff --git a/src/api/notification/email-templates/index.ts b/src/api/notification/email-templates/index.ts index 0939b5bbe..e65463e73 100644 --- a/src/api/notification/email-templates/index.ts +++ b/src/api/notification/email-templates/index.ts @@ -119,6 +119,11 @@ type RejectionDeclarationVariables = DeclarationCommonVariables & { name: string } +type AllUserNotificationVariables = { + subject: string + body: string +} + const templates = { 'onboarding-invite': { type: 'onboarding-invite', @@ -223,6 +228,13 @@ const templates = { type: 'deathRejectionNotification', subject: 'Death declaration required update', template: readDeathTemplate('rejection') + }, + allUserNotification: { + type: 'allUserNotification', + subject: '', // Subject defined from National Sys Admin Dashboard + template: readOtherTemplate( + 'all-user-notification' + ) } } as const diff --git a/src/api/notification/email-templates/other/all-user-notification.html b/src/api/notification/email-templates/other/all-user-notification.html new file mode 100644 index 000000000..7e2b34391 --- /dev/null +++ b/src/api/notification/email-templates/other/all-user-notification.html @@ -0,0 +1,53 @@ + + + + + + + + + + country_logo +

{{subject}}

+ +

+ {{body}} +

+
+

+ Best regards, +
+ Farajaland CRVS Team +

+
+ This is an automated message. Please do not reply to this email. + + + diff --git a/src/api/notification/handler.ts b/src/api/notification/handler.ts index bc10e54d8..9ad69f1ce 100644 --- a/src/api/notification/handler.ts +++ b/src/api/notification/handler.ts @@ -40,6 +40,7 @@ type EmailNotificationPayload = { } recipient: { email: string + bcc?: string[] } type: 'user' | 'informant' locale: string @@ -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) @@ -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 diff --git a/src/index.ts b/src/index.ts index 22156c4c0..0c332da7d 100644 --- a/src/index.ts +++ b/src/index.ts @@ -452,6 +452,7 @@ export async function createServer() { handler: notificationHandler, options: { tags: ['api'], + auth: false, validate: { payload: notificationSchema }, diff --git a/src/translations/client.csv b/src/translations/client.csv index ee66f59bc..1cab2d872 100644 --- a/src/translations/client.csv +++ b/src/translations/client.csv @@ -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 @@ -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 @@ -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}) @@ -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