feature/IO-2979-DST-Handling
- Add LocalStack and Adjust local Emailing Signed-off-by: Dave Richer <dave@imexsystems.ca>
This commit is contained in:
@@ -7,7 +7,6 @@ _reference
|
|||||||
client
|
client
|
||||||
redis/dockerdata
|
redis/dockerdata
|
||||||
hasura
|
hasura
|
||||||
node_modules
|
|
||||||
|
|
||||||
# Files to exclude
|
# Files to exclude
|
||||||
.ebignore
|
.ebignore
|
||||||
|
|||||||
@@ -25,6 +25,9 @@ RUN dnf install -y \
|
|||||||
# Set the working directory
|
# Set the working directory
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
# This is because our test route uses a git commit hash
|
||||||
|
RUN git config --global --add safe.directory /app
|
||||||
|
|
||||||
# Copy package.json and package-lock.json
|
# Copy package.json and package-lock.json
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
|
||||||
@@ -32,7 +35,7 @@ COPY package*.json ./
|
|||||||
RUN npm install -g nodemon
|
RUN npm install -g nodemon
|
||||||
|
|
||||||
# Install dependencies
|
# Install dependencies
|
||||||
RUN npm install --omit=dev
|
RUN npm ci
|
||||||
|
|
||||||
# Copy the rest of your application code
|
# Copy the rest of your application code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
@@ -138,6 +138,10 @@ sudo sysctl -p
|
|||||||
# Install Docker and Docker Compose in WSL2
|
# Install Docker and Docker Compose in WSL2
|
||||||
- https://docs.docker.com/desktop/wsl/
|
- https://docs.docker.com/desktop/wsl/
|
||||||
|
|
||||||
|
# Local Stack
|
||||||
|
- LocalStack Front end (Optional) - https://apps.microsoft.com/detail/9ntrnft9zws2?hl=en-us&gl=US
|
||||||
|
- http://localhost:4566/_aws/ses will allow you to see emails sent
|
||||||
|
|
||||||
# Docker Commands
|
# Docker Commands
|
||||||
|
|
||||||
## General `docker-compose` Commands:
|
## General `docker-compose` Commands:
|
||||||
@@ -150,6 +154,7 @@ sudo sysctl -p
|
|||||||
7. View running Containers: `docker-compose ps`
|
7. View running Containers: `docker-compose ps`
|
||||||
8. View a specific containers logs: `docker-compose logs <container-name>`
|
8. View a specific containers logs: `docker-compose logs <container-name>`
|
||||||
9. Scale services (multiple instances of a service): `docker-compose up --scale <container-name>=<instances number> -d`
|
9. Scale services (multiple instances of a service): `docker-compose up --scale <container-name>=<instances number> -d`
|
||||||
|
10. Watch a specific containers logs in realtime with timestamps: `docker-compose logs -f --timestamps <container-name>`
|
||||||
|
|
||||||
## Volume Management Commands
|
## Volume Management Commands
|
||||||
1. List Docker volumes: `docker volume ls`
|
1. List Docker volumes: `docker volume ls`
|
||||||
|
|||||||
@@ -314,8 +314,8 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
|||||||
try {
|
try {
|
||||||
const userEmail = yield select((state) => state.user.currentUser.email);
|
const userEmail = yield select((state) => state.user.currentUser.email);
|
||||||
try {
|
try {
|
||||||
//console.log("Setting shop timezone.");
|
console.log("Setting shop timezone.");
|
||||||
// dayjs.tz.setDefault(payload.timezone);
|
day.tz.setDefault(payload.timezone);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
#############################
|
#############################
|
||||||
# Ports Exposed
|
# Ports Exposed
|
||||||
# 4000 - Imex Node API
|
# 4000 - Imex Node API
|
||||||
# 3333 - SocketIO Admin-UI
|
# 4556 - LocalStack (Local AWS)
|
||||||
# 3334 - Redis-Insights
|
# 3333 - SocketIO Admin-UI (Optional)
|
||||||
|
# 3334 - Redis-Insights (Optional)
|
||||||
#############################
|
#############################
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
|
||||||
|
# Redis Node 1
|
||||||
redis-node-1:
|
redis-node-1:
|
||||||
build:
|
build:
|
||||||
context: ./redis
|
context: ./redis
|
||||||
@@ -22,7 +25,8 @@ services:
|
|||||||
interval: 10s
|
interval: 10s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
|
# Redis Node 2
|
||||||
redis-node-2:
|
redis-node-2:
|
||||||
build:
|
build:
|
||||||
context: ./redis
|
context: ./redis
|
||||||
@@ -40,6 +44,7 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
|
# Redis Node 3
|
||||||
redis-node-3:
|
redis-node-3:
|
||||||
build:
|
build:
|
||||||
context: ./redis
|
context: ./redis
|
||||||
@@ -57,6 +62,52 @@ services:
|
|||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 10
|
retries: 10
|
||||||
|
|
||||||
|
# LocalStack: Used to emulate AWS services locally, currently setup for SES
|
||||||
|
localstack:
|
||||||
|
image: localstack/localstack
|
||||||
|
container_name: localstack
|
||||||
|
hostname: localstack
|
||||||
|
networks:
|
||||||
|
- redis-cluster-net
|
||||||
|
restart: always
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock
|
||||||
|
environment:
|
||||||
|
- SERVICES=ses
|
||||||
|
- DEBUG=1
|
||||||
|
- AWS_ACCESS_KEY_ID=test
|
||||||
|
- AWS_SECRET_ACCESS_KEY=test
|
||||||
|
- AWS_DEFAULT_REGION=ca-central-1
|
||||||
|
ports:
|
||||||
|
- "4566:4566"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
|
# AWS-CLI - Used in conjunction with LocalStack to set required permission to send emails
|
||||||
|
aws-cli:
|
||||||
|
image: amazon/aws-cli
|
||||||
|
container_name: aws-cli
|
||||||
|
hostname: aws-cli
|
||||||
|
networks:
|
||||||
|
- redis-cluster-net
|
||||||
|
depends_on:
|
||||||
|
localstack:
|
||||||
|
condition: service_healthy
|
||||||
|
environment:
|
||||||
|
- AWS_ACCESS_KEY_ID=test
|
||||||
|
- AWS_SECRET_ACCESS_KEY=test
|
||||||
|
- AWS_DEFAULT_REGION=ca-central-1
|
||||||
|
entrypoint: /bin/sh -c
|
||||||
|
command: >
|
||||||
|
"
|
||||||
|
aws --endpoint-url=http://localstack:4566 ses verify-domain-identity --domain imex.online --region ca-central-1
|
||||||
|
aws --endpoint-url=http://localstack:4566 ses verify-email-identity --email-address noreply@imex.online --region ca-central-1
|
||||||
|
"
|
||||||
|
# Node App: The Main IMEX API
|
||||||
node-app:
|
node-app:
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
@@ -73,37 +124,44 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
redis-node-3:
|
redis-node-3:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
|
localstack:
|
||||||
|
condition: service_healthy
|
||||||
|
aws-cli:
|
||||||
|
condition: service_completed_successfully
|
||||||
ports:
|
ports:
|
||||||
- "4000:4000"
|
- "4000:4000"
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app
|
||||||
- /app/node_modules
|
- node-app-npm-cache:/app/node_modules
|
||||||
|
|
||||||
socketio-admin-ui:
|
# ## Optional Container to Observe SocketIO data
|
||||||
image: maitrungduc1410/socket.io-admin-ui
|
# socketio-admin-ui:
|
||||||
container_name: socketio-admin-ui
|
# image: maitrungduc1410/socket.io-admin-ui
|
||||||
networks:
|
# container_name: socketio-admin-ui
|
||||||
- redis-cluster-net
|
# networks:
|
||||||
ports:
|
# - redis-cluster-net
|
||||||
- "3333:80"
|
# ports:
|
||||||
|
# - "3333:80"
|
||||||
redis-insight:
|
|
||||||
image: redislabs/redisinsight:latest
|
# ##Optional Container to Observe Redis Cluster Data
|
||||||
container_name: redis-insight
|
# redis-insight:
|
||||||
hostname: redis-insight
|
# image: redislabs/redisinsight:latest
|
||||||
restart: always
|
# container_name: redis-insight
|
||||||
ports:
|
# hostname: redis-insight
|
||||||
- "3334:5540"
|
# restart: always
|
||||||
networks:
|
# ports:
|
||||||
- redis-cluster-net
|
# - "3334:5540"
|
||||||
volumes:
|
# networks:
|
||||||
- redis-insight-data:/db
|
# - redis-cluster-net
|
||||||
|
# volumes:
|
||||||
|
# - redis-insight-data:/db
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
redis-cluster-net:
|
redis-cluster-net:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
|
node-app-npm-cache:
|
||||||
redis-node-1-data:
|
redis-node-1-data:
|
||||||
redis-node-2-data:
|
redis-node-2-data:
|
||||||
redis-node-3-data:
|
redis-node-3-data:
|
||||||
|
|||||||
49
server/email/mailer.js
Normal file
49
server/email/mailer.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
const { isString, isEmpty } = require("lodash");
|
||||||
|
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
||||||
|
const { default: InstanceManager } = require("../utils/instanceMgr");
|
||||||
|
const aws = require("@aws-sdk/client-ses");
|
||||||
|
const nodemailer = require("nodemailer");
|
||||||
|
|
||||||
|
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
|
||||||
|
|
||||||
|
const sesConfig = {
|
||||||
|
apiVersion: "latest",
|
||||||
|
credentials: defaultProvider(),
|
||||||
|
region: isLocal
|
||||||
|
? "ca-central-1"
|
||||||
|
: InstanceManager({
|
||||||
|
imex: "ca-central-1",
|
||||||
|
rome: "us-east-2"
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
if (isLocal) {
|
||||||
|
sesConfig.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
|
||||||
|
console.log(`SES Mailer set to LocalStack end point: ${sesConfig.endpoint}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ses = new aws.SES(sesConfig);
|
||||||
|
|
||||||
|
let transporter = nodemailer.createTransport({
|
||||||
|
SES: { ses, aws }
|
||||||
|
});
|
||||||
|
|
||||||
|
if (isLocal) {
|
||||||
|
// Wrap the sendMail function to log the email contents to the console in local environment
|
||||||
|
const originalSendMail = transporter.sendMail.bind(transporter);
|
||||||
|
transporter.sendMail = async (mailOptions) => {
|
||||||
|
try {
|
||||||
|
const result = await originalSendMail(mailOptions);
|
||||||
|
console.log(
|
||||||
|
`Email sent successfully - From: ${result?.envelope?.from} - To: ${result?.envelope?.to} - MessageID ${result.messageId} - Check LocalStack to see message`
|
||||||
|
);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to send email:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = transporter;
|
||||||
@@ -3,30 +3,13 @@ require("dotenv").config({
|
|||||||
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
||||||
});
|
});
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
let nodemailer = require("nodemailer");
|
|
||||||
let aws = require("@aws-sdk/client-ses");
|
|
||||||
let { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
|
||||||
const InstanceManager = require("../utils/instanceMgr").default;
|
const InstanceManager = require("../utils/instanceMgr").default;
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
const client = require("../graphql-client/graphql-client").client;
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
const queries = require("../graphql-client/queries");
|
const queries = require("../graphql-client/queries");
|
||||||
const { isObject } = require("lodash");
|
const { isObject } = require("lodash");
|
||||||
const generateEmailTemplate = require("./generateTemplate");
|
const generateEmailTemplate = require("./generateTemplate");
|
||||||
const moment = require("moment");
|
const mailer = require("./mailer");
|
||||||
|
|
||||||
const ses = new aws.SES({
|
|
||||||
// The key apiVersion is no longer supported in v3, and can be removed.
|
|
||||||
// @deprecated The client uses the "latest" apiVersion.
|
|
||||||
apiVersion: "latest",
|
|
||||||
credentials: defaultProvider(),
|
|
||||||
region: InstanceManager({
|
|
||||||
imex: "ca-central-1",
|
|
||||||
rome: "us-east-2"
|
|
||||||
})
|
|
||||||
});
|
|
||||||
let transporter = nodemailer.createTransport({
|
|
||||||
SES: { ses, aws }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get the image from the URL and return it as a base64 string
|
// Get the image from the URL and return it as a base64 string
|
||||||
const getImage = async (imageUrl) => {
|
const getImage = async (imageUrl) => {
|
||||||
@@ -66,7 +49,7 @@ const logEmail = async (req, email) => {
|
|||||||
const sendServerEmail = async ({ subject, text }) => {
|
const sendServerEmail = async ({ subject, text }) => {
|
||||||
if (process.env.NODE_ENV === undefined) return;
|
if (process.env.NODE_ENV === undefined) return;
|
||||||
try {
|
try {
|
||||||
transporter.sendMail(
|
mailer.sendMail(
|
||||||
{
|
{
|
||||||
from: InstanceManager({
|
from: InstanceManager({
|
||||||
imex: `ImEX Online API - ${process.env.NODE_ENV} <noreply@imex.online>`,
|
imex: `ImEX Online API - ${process.env.NODE_ENV} <noreply@imex.online>`,
|
||||||
@@ -98,7 +81,7 @@ const sendServerEmail = async ({ subject, text }) => {
|
|||||||
|
|
||||||
const sendProManagerWelcomeEmail = async ({ to, subject, html }) => {
|
const sendProManagerWelcomeEmail = async ({ to, subject, html }) => {
|
||||||
try {
|
try {
|
||||||
await transporter.sendMail({
|
await mailer.sendMail({
|
||||||
from: `ProManager <noreply@promanager.web-est.com>`,
|
from: `ProManager <noreply@promanager.web-est.com>`,
|
||||||
to,
|
to,
|
||||||
subject,
|
subject,
|
||||||
@@ -112,7 +95,7 @@ const sendProManagerWelcomeEmail = async ({ to, subject, html }) => {
|
|||||||
|
|
||||||
const sendTaskEmail = async ({ to, subject, type = "text", html, text, attachments }) => {
|
const sendTaskEmail = async ({ to, subject, type = "text", html, text, attachments }) => {
|
||||||
try {
|
try {
|
||||||
transporter.sendMail(
|
mailer.sendMail(
|
||||||
{
|
{
|
||||||
from: InstanceManager({
|
from: InstanceManager({
|
||||||
imex: `ImEX Online <noreply@imex.online>`,
|
imex: `ImEX Online <noreply@imex.online>`,
|
||||||
@@ -166,7 +149,7 @@ const sendEmail = async (req, res) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
transporter.sendMail(
|
mailer.sendMail(
|
||||||
{
|
{
|
||||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||||
replyTo: req.body.ReplyTo.Email,
|
replyTo: req.body.ReplyTo.Email,
|
||||||
@@ -280,7 +263,7 @@ const emailBounce = async (req, res) => {
|
|||||||
status: "Bounced",
|
status: "Bounced",
|
||||||
context: message.bounce?.bouncedRecipients
|
context: message.bounce?.bouncedRecipients
|
||||||
});
|
});
|
||||||
transporter.sendMail(
|
mailer.sendMail(
|
||||||
{
|
{
|
||||||
from: InstanceMgr({
|
from: InstanceMgr({
|
||||||
imex: `ImEX Online <noreply@imex.online>`,
|
imex: `ImEX Online <noreply@imex.online>`,
|
||||||
|
|||||||
@@ -2,9 +2,6 @@ const path = require("path");
|
|||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
||||||
});
|
});
|
||||||
let nodemailer = require("nodemailer");
|
|
||||||
let aws = require("@aws-sdk/client-ses");
|
|
||||||
let { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
|
||||||
const InstanceManager = require("../utils/instanceMgr").default;
|
const InstanceManager = require("../utils/instanceMgr").default;
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
const client = require("../graphql-client/graphql-client").client;
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
@@ -12,20 +9,7 @@ const queries = require("../graphql-client/queries");
|
|||||||
const generateEmailTemplate = require("./generateTemplate");
|
const generateEmailTemplate = require("./generateTemplate");
|
||||||
const moment = require("moment-timezone");
|
const moment = require("moment-timezone");
|
||||||
const { taskEmailQueue } = require("./tasksEmailsQueue");
|
const { taskEmailQueue } = require("./tasksEmailsQueue");
|
||||||
|
const mailer = require("./mailer");
|
||||||
const ses = new aws.SES({
|
|
||||||
apiVersion: "latest",
|
|
||||||
credentials: defaultProvider(),
|
|
||||||
region: InstanceManager({
|
|
||||||
imex: "ca-central-1",
|
|
||||||
rome: "us-east-2"
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
const transporter = nodemailer.createTransport({
|
|
||||||
SES: { ses, aws },
|
|
||||||
sendingRate: InstanceManager({ imex: 40, rome: 10 })
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize the Tasks Email Queue
|
// Initialize the Tasks Email Queue
|
||||||
const tasksEmailQueue = taskEmailQueue();
|
const tasksEmailQueue = taskEmailQueue();
|
||||||
@@ -124,6 +108,7 @@ const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, j
|
|||||||
* @param html
|
* @param html
|
||||||
* @param taskIds
|
* @param taskIds
|
||||||
* @param successCallback
|
* @param successCallback
|
||||||
|
* @param requestInstance
|
||||||
*/
|
*/
|
||||||
const sendMail = (type, to, subject, html, taskIds, successCallback, requestInstance) => {
|
const sendMail = (type, to, subject, html, taskIds, successCallback, requestInstance) => {
|
||||||
const fromEmails = InstanceManager({
|
const fromEmails = InstanceManager({
|
||||||
@@ -134,7 +119,7 @@ const sendMail = (type, to, subject, html, taskIds, successCallback, requestInst
|
|||||||
: "Rome Online <noreply@romeonline.io>"
|
: "Rome Online <noreply@romeonline.io>"
|
||||||
});
|
});
|
||||||
|
|
||||||
transporter.sendMail(
|
mailer.sendMail(
|
||||||
{
|
{
|
||||||
from: fromEmails,
|
from: fromEmails,
|
||||||
to,
|
to,
|
||||||
@@ -151,8 +136,6 @@ const sendMail = (type, to, subject, html, taskIds, successCallback, requestInst
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
// }
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user