Compare commits
22 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1d98de6d4d | ||
|
|
85e82b85ea | ||
|
|
23467280b4 | ||
|
|
aedad1c48f | ||
|
|
05cc4dd188 | ||
|
|
ea6351ea06 | ||
|
|
87d3ceb408 | ||
|
|
d08dd2b506 | ||
|
|
8a047d14a1 | ||
|
|
e103772aa4 | ||
|
|
c332699dc8 | ||
|
|
25e6e61d10 | ||
|
|
cdcd6b636a | ||
|
|
7879591bcf | ||
|
|
7fc6556866 | ||
|
|
3f5489ce7e | ||
|
|
5a90854861 | ||
|
|
8347a8c098 | ||
|
|
2bf074d85a | ||
|
|
50d47cd679 | ||
|
|
3a4e06eaa2 | ||
|
|
74d95e7cbb |
@@ -8,7 +8,6 @@ client
|
||||
redis/dockerdata
|
||||
hasura
|
||||
node_modules
|
||||
|
||||
# Files to exclude
|
||||
.ebignore
|
||||
.editorconfig
|
||||
|
||||
@@ -25,14 +25,17 @@ RUN dnf install -y \
|
||||
# Set the working directory
|
||||
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 ./
|
||||
COPY package.json ./
|
||||
|
||||
# Install Nodemon
|
||||
RUN npm install -g nodemon
|
||||
|
||||
# Install dependencies
|
||||
RUN npm install --omit=dev
|
||||
RUN npm i --no-package-lock
|
||||
|
||||
# Copy the rest of your application code
|
||||
COPY . .
|
||||
|
||||
@@ -138,6 +138,10 @@ sudo sysctl -p
|
||||
# Install Docker and Docker Compose in WSL2
|
||||
- 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
|
||||
|
||||
## General `docker-compose` Commands:
|
||||
@@ -150,6 +154,7 @@ sudo sysctl -p
|
||||
7. View running Containers: `docker-compose ps`
|
||||
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`
|
||||
10. Watch a specific containers logs in realtime with timestamps: `docker-compose logs -f --timestamps <container-name>`
|
||||
|
||||
## Volume Management Commands
|
||||
1. List Docker volumes: `docker volume ls`
|
||||
|
||||
@@ -3,13 +3,15 @@ import axios from "axios";
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
|
||||
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
|
||||
|
||||
export default function GlobalSearchOs() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [data, setData] = useState(false);
|
||||
|
||||
@@ -177,7 +179,18 @@ export default function GlobalSearchOs() {
|
||||
};
|
||||
|
||||
return (
|
||||
<AutoComplete options={data} onSearch={handleSearch} defaultActiveFirstOption onClear={() => setData([])}>
|
||||
<AutoComplete
|
||||
options={data}
|
||||
onSearch={handleSearch}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== "Enter") return;
|
||||
const firstUrlForSearch = data?.[0]?.options?.[0]?.label?.props?.to;
|
||||
if (!firstUrlForSearch) return;
|
||||
navigate(firstUrlForSearch);
|
||||
}}
|
||||
defaultActiveFirstOption
|
||||
onClear={() => setData([])}
|
||||
>
|
||||
<Input.Search
|
||||
size="large"
|
||||
placeholder={t("general.labels.globalsearch")}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { AutoComplete, Divider, Input, Space } from "antd";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Link, useNavigate } from "react-router-dom";
|
||||
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
|
||||
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
@@ -13,6 +13,7 @@ import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.compon
|
||||
export default function GlobalSearch() {
|
||||
const { t } = useTranslation();
|
||||
const [callSearch, { loading, error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const executeSearch = (v) => {
|
||||
if (v && v.variables.search && v.variables.search !== "" && v.variables.search.length >= 3) callSearch(v);
|
||||
@@ -20,7 +21,6 @@ export default function GlobalSearch() {
|
||||
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
|
||||
|
||||
const handleSearch = (value) => {
|
||||
console.log("Handle Search");
|
||||
debouncedExecuteSearch({ variables: { search: value } });
|
||||
};
|
||||
|
||||
@@ -156,7 +156,17 @@ export default function GlobalSearch() {
|
||||
if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<AutoComplete options={options} onSearch={handleSearch} defaultActiveFirstOption>
|
||||
<AutoComplete
|
||||
options={options}
|
||||
onSearch={handleSearch}
|
||||
defaultActiveFirstOption
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== "Enter") return;
|
||||
const firstUrlForSearch = options?.[0]?.options?.[0]?.label?.props?.to;
|
||||
if (!firstUrlForSearch) return;
|
||||
navigate(firstUrlForSearch);
|
||||
}}
|
||||
>
|
||||
<Input.Search
|
||||
size="large"
|
||||
placeholder={t("general.labels.globalsearch")}
|
||||
|
||||
@@ -219,7 +219,7 @@ export function JobsExportAllButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled || jobIds?.length > 10}>
|
||||
{t("jobs.actions.exportselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -242,7 +242,8 @@ export function PartsOrderListTableComponent({
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
render: (text, record) => recordActions(record, true)
|
||||
render: (text, record) => recordActions(record, true),
|
||||
id: "parts-order-list-table-actions"
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -200,7 +200,7 @@ export function PayableExportAll({
|
||||
);
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled || billids?.length > 10}>
|
||||
{t("jobs.actions.exportselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -180,7 +180,7 @@ export function PaymentsExportAllButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled || paymentIds?.length > 10}>
|
||||
{t("jobs.actions.exportselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -314,8 +314,8 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
try {
|
||||
const userEmail = yield select((state) => state.user.currentUser.email);
|
||||
try {
|
||||
//console.log("Setting shop timezone.");
|
||||
// dayjs.tz.setDefault(payload.timezone);
|
||||
console.log("Setting shop timezone.");
|
||||
day.tz.setDefault(payload.timezone);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
#############################
|
||||
# Ports Exposed
|
||||
# 4000 - Imex Node API
|
||||
# 3333 - SocketIO Admin-UI
|
||||
# 3334 - Redis-Insights
|
||||
# 4000 - Imex Node API
|
||||
# 4556 - LocalStack (Local AWS)
|
||||
# 3333 - SocketIO Admin-UI (Optional)
|
||||
# 3334 - Redis-Insights (Optional)
|
||||
#############################
|
||||
|
||||
services:
|
||||
|
||||
# Redis Node 1
|
||||
redis-node-1:
|
||||
build:
|
||||
context: ./redis
|
||||
container_name: redis-node-1
|
||||
hostname: redis-node-1
|
||||
restart: always
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- redis-cluster-net
|
||||
volumes:
|
||||
@@ -22,13 +25,14 @@ services:
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
|
||||
# Redis Node 2
|
||||
redis-node-2:
|
||||
build:
|
||||
context: ./redis
|
||||
container_name: redis-node-2
|
||||
hostname: redis-node-2
|
||||
restart: always
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- redis-cluster-net
|
||||
volumes:
|
||||
@@ -40,12 +44,13 @@ services:
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# Redis Node 3
|
||||
redis-node-3:
|
||||
build:
|
||||
context: ./redis
|
||||
container_name: redis-node-3
|
||||
hostname: redis-node-3
|
||||
restart: always
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- redis-cluster-net
|
||||
volumes:
|
||||
@@ -57,6 +62,56 @@ services:
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
# LocalStack: Used to emulate AWS services locally, currently setup for SES
|
||||
# Notes: Set the ENV Debug to 1 for additional logging
|
||||
localstack:
|
||||
image: localstack/localstack
|
||||
container_name: localstack
|
||||
hostname: localstack
|
||||
networks:
|
||||
- redis-cluster-net
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- SERVICES=ses
|
||||
- DEBUG=0
|
||||
- AWS_ACCESS_KEY_ID=test
|
||||
- AWS_SECRET_ACCESS_KEY=test
|
||||
- AWS_DEFAULT_REGION=ca-central-1
|
||||
- EXTRA_CORS_ALLOWED_HEADERS=Authorization,Content-Type
|
||||
- EXTRA_CORS_ALLOWED_ORIGINS=*
|
||||
- EXTRA_CORS_EXPOSE_HEADERS=Authorization,Content-Type
|
||||
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:
|
||||
build:
|
||||
context: .
|
||||
@@ -73,37 +128,44 @@ services:
|
||||
condition: service_healthy
|
||||
redis-node-3:
|
||||
condition: service_healthy
|
||||
localstack:
|
||||
condition: service_healthy
|
||||
aws-cli:
|
||||
condition: service_completed_successfully
|
||||
ports:
|
||||
- "4000:4000"
|
||||
volumes:
|
||||
- .:/app
|
||||
- /app/node_modules
|
||||
- node-app-npm-cache:/app/node_modules
|
||||
|
||||
socketio-admin-ui:
|
||||
image: maitrungduc1410/socket.io-admin-ui
|
||||
container_name: socketio-admin-ui
|
||||
networks:
|
||||
- redis-cluster-net
|
||||
ports:
|
||||
- "3333:80"
|
||||
|
||||
redis-insight:
|
||||
image: redislabs/redisinsight:latest
|
||||
container_name: redis-insight
|
||||
hostname: redis-insight
|
||||
restart: always
|
||||
ports:
|
||||
- "3334:5540"
|
||||
networks:
|
||||
- redis-cluster-net
|
||||
volumes:
|
||||
- redis-insight-data:/db
|
||||
# ## Optional Container to Observe SocketIO data
|
||||
# socketio-admin-ui:
|
||||
# image: maitrungduc1410/socket.io-admin-ui
|
||||
# container_name: socketio-admin-ui
|
||||
# networks:
|
||||
# - redis-cluster-net
|
||||
# ports:
|
||||
# - "3333:80"
|
||||
|
||||
# ##Optional Container to Observe Redis Cluster Data
|
||||
# redis-insight:
|
||||
# image: redislabs/redisinsight:latest
|
||||
# container_name: redis-insight
|
||||
# hostname: redis-insight
|
||||
# restart: unless-stopped
|
||||
# ports:
|
||||
# - "3334:5540"
|
||||
# networks:
|
||||
# - redis-cluster-net
|
||||
# volumes:
|
||||
# - redis-insight-data:/db
|
||||
|
||||
networks:
|
||||
redis-cluster-net:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
node-app-npm-cache:
|
||||
redis-node-1-data:
|
||||
redis-node-2-data:
|
||||
redis-node-3-data:
|
||||
|
||||
35
nodemon.json
Normal file
35
nodemon.json
Normal file
@@ -0,0 +1,35 @@
|
||||
{
|
||||
"watch": [
|
||||
"server.js",
|
||||
"server"
|
||||
],
|
||||
"ignore": [
|
||||
"client",
|
||||
".circleci",
|
||||
".platform",
|
||||
".idea",
|
||||
".vscode",
|
||||
"_reference",
|
||||
"firebase",
|
||||
"hasura",
|
||||
"logs",
|
||||
"redis",
|
||||
".dockerignore",
|
||||
".ebignore",
|
||||
".editorconfig",
|
||||
".env.development",
|
||||
".env.development.rome",
|
||||
".eslintrc.json",
|
||||
".gitignore",
|
||||
".npmmrc",
|
||||
".prettierrc.js",
|
||||
"bodyshop_translations.babel",
|
||||
"Dockerfile",
|
||||
"ecosystem.config.js",
|
||||
"job-totals-testing-util.js",
|
||||
"nodemon.json",
|
||||
"package.json",
|
||||
"package-lock.json",
|
||||
"setadmin.js"
|
||||
]
|
||||
}
|
||||
31
server/email/mailer.js
Normal file
31
server/email/mailer.js
Normal file
@@ -0,0 +1,31 @@
|
||||
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 }
|
||||
});
|
||||
|
||||
module.exports = transporter;
|
||||
@@ -3,30 +3,13 @@ require("dotenv").config({
|
||||
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
|
||||
});
|
||||
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 logger = require("../utils/logger");
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
const queries = require("../graphql-client/queries");
|
||||
const { isObject } = require("lodash");
|
||||
const generateEmailTemplate = require("./generateTemplate");
|
||||
const moment = require("moment");
|
||||
|
||||
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 }
|
||||
});
|
||||
const mailer = require("./mailer");
|
||||
|
||||
// Get the image from the URL and return it as a base64 string
|
||||
const getImage = async (imageUrl) => {
|
||||
@@ -66,7 +49,7 @@ const logEmail = async (req, email) => {
|
||||
const sendServerEmail = async ({ subject, text }) => {
|
||||
if (process.env.NODE_ENV === undefined) return;
|
||||
try {
|
||||
transporter.sendMail(
|
||||
mailer.sendMail(
|
||||
{
|
||||
from: InstanceManager({
|
||||
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 }) => {
|
||||
try {
|
||||
await transporter.sendMail({
|
||||
await mailer.sendMail({
|
||||
from: `ProManager <noreply@promanager.web-est.com>`,
|
||||
to,
|
||||
subject,
|
||||
@@ -112,7 +95,7 @@ const sendProManagerWelcomeEmail = async ({ to, subject, html }) => {
|
||||
|
||||
const sendTaskEmail = async ({ to, subject, type = "text", html, text, attachments }) => {
|
||||
try {
|
||||
transporter.sendMail(
|
||||
mailer.sendMail(
|
||||
{
|
||||
from: InstanceManager({
|
||||
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}>`,
|
||||
replyTo: req.body.ReplyTo.Email,
|
||||
@@ -280,7 +263,7 @@ const emailBounce = async (req, res) => {
|
||||
status: "Bounced",
|
||||
context: message.bounce?.bouncedRecipients
|
||||
});
|
||||
transporter.sendMail(
|
||||
mailer.sendMail(
|
||||
{
|
||||
from: InstanceMgr({
|
||||
imex: `ImEX Online <noreply@imex.online>`,
|
||||
|
||||
@@ -2,30 +2,14 @@ const path = require("path");
|
||||
require("dotenv").config({
|
||||
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 logger = require("../utils/logger");
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
const queries = require("../graphql-client/queries");
|
||||
const generateEmailTemplate = require("./generateTemplate");
|
||||
const moment = require("moment");
|
||||
const moment = require("moment-timezone");
|
||||
const { taskEmailQueue } = require("./tasksEmailsQueue");
|
||||
|
||||
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 })
|
||||
});
|
||||
const mailer = require("./mailer");
|
||||
|
||||
// Initialize the Tasks Email Queue
|
||||
const tasksEmailQueue = taskEmailQueue();
|
||||
@@ -41,26 +25,32 @@ const tasksEmailQueueCleanup = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
if (process.env.NODE_ENV !== "development") {
|
||||
// Handling SIGINT (e.g., Ctrl+C)
|
||||
process.on("SIGINT", async () => {
|
||||
await tasksEmailQueueCleanup();
|
||||
});
|
||||
// Handling SIGTERM (e.g., sent by system shutdown)
|
||||
process.on("SIGTERM", async () => {
|
||||
await tasksEmailQueueCleanup();
|
||||
});
|
||||
// Handling uncaught exceptions
|
||||
process.on("uncaughtException", async (err) => {
|
||||
await tasksEmailQueueCleanup();
|
||||
throw err;
|
||||
});
|
||||
// Handling unhandled promise rejections
|
||||
process.on("unhandledRejection", async (reason, promise) => {
|
||||
await tasksEmailQueueCleanup();
|
||||
throw reason;
|
||||
});
|
||||
}
|
||||
// if (process.env.NODE_ENV !== "development") {
|
||||
// // Handling SIGINT (e.g., Ctrl+C)
|
||||
// process.on("SIGINT", async () => {
|
||||
// console.log("Handling SIGNIT For Tasks Cleanup");
|
||||
// await tasksEmailQueueCleanup();
|
||||
// process.exit(0);
|
||||
// });
|
||||
// // Handling SIGTERM (e.g., sent by system shutdown)
|
||||
// process.on("SIGTERM", async () => {
|
||||
// console.log("Handling SIGTERM For Tasks Cleanup");
|
||||
// await tasksEmailQueueCleanup();
|
||||
// process.exit(0);
|
||||
// });
|
||||
// // Handling uncaught exceptions
|
||||
// process.on("uncaughtException", async (err) => {
|
||||
// console.log("Handling uncaughtException For Tasks Cleanup");
|
||||
// await tasksEmailQueueCleanup();
|
||||
// process.exit(1);
|
||||
// });
|
||||
// // Handling unhandled promise rejections
|
||||
// process.on("unhandledRejection", async (reason, promise) => {
|
||||
// console.log("Handling unhandledRejection For Tasks Cleanup");
|
||||
// await tasksEmailQueueCleanup();
|
||||
// process.exit(1);
|
||||
// });
|
||||
// }
|
||||
|
||||
/**
|
||||
* Format the date for the email.
|
||||
@@ -106,12 +96,13 @@ const getEndpoints = (bodyshop) =>
|
||||
: "https://romeonline.io"
|
||||
});
|
||||
|
||||
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId) => {
|
||||
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId, dateLine) => {
|
||||
const endPoints = getEndpoints(bodyshop);
|
||||
return {
|
||||
header: title,
|
||||
subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)}`,
|
||||
body: `Reference: ${job.ro_number || "N/A"} | ${job.ownr_co_nm ? job.ownr_co_nm : `${job.ownr_fn || ""} ${job.ownr_ln || ""}`.trim()} | ${`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim()}<br>${description ? description.concat("<br>") : ""}<a href="${endPoints}/manage/tasks/alltasks?taskid=${taskId}">View this task.</a>`
|
||||
body: `Reference: ${job.ro_number || "N/A"} | ${job.ownr_co_nm ? job.ownr_co_nm : `${job.ownr_fn || ""} ${job.ownr_ln || ""}`.trim()} | ${`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim()}<br>${description ? description.concat("<br>") : ""}<a href="${endPoints}/manage/tasks/alltasks?taskid=${taskId}">View this task.</a>`,
|
||||
dateLine
|
||||
};
|
||||
};
|
||||
|
||||
@@ -123,6 +114,7 @@ const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, j
|
||||
* @param html
|
||||
* @param taskIds
|
||||
* @param successCallback
|
||||
* @param requestInstance
|
||||
*/
|
||||
const sendMail = (type, to, subject, html, taskIds, successCallback, requestInstance) => {
|
||||
const fromEmails = InstanceManager({
|
||||
@@ -133,7 +125,7 @@ const sendMail = (type, to, subject, html, taskIds, successCallback, requestInst
|
||||
: "Rome Online <noreply@romeonline.io>"
|
||||
});
|
||||
|
||||
transporter.sendMail(
|
||||
mailer.sendMail(
|
||||
{
|
||||
from: fromEmails,
|
||||
to,
|
||||
@@ -150,8 +142,6 @@ const sendMail = (type, to, subject, html, taskIds, successCallback, requestInst
|
||||
}
|
||||
}
|
||||
);
|
||||
// }
|
||||
// });
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -176,6 +166,8 @@ const taskAssignedEmail = async (req, res) => {
|
||||
id: newTask.id
|
||||
});
|
||||
|
||||
const dateLine = moment().tz(tasks_by_pk.bodyshop.timezone).format("M/DD/YYYY @ hh:mm a");
|
||||
|
||||
sendMail(
|
||||
"assigned",
|
||||
tasks_by_pk.assigned_to_employee.user_email,
|
||||
@@ -188,7 +180,8 @@ const taskAssignedEmail = async (req, res) => {
|
||||
newTask.due_date,
|
||||
tasks_by_pk.bodyshop,
|
||||
tasks_by_pk.job,
|
||||
newTask.id
|
||||
newTask.id,
|
||||
dateLine
|
||||
)
|
||||
),
|
||||
null,
|
||||
@@ -257,6 +250,8 @@ const tasksRemindEmail = async (req, res) => {
|
||||
|
||||
const taskIds = groupedTasks[recipient.email].map((task) => task.id);
|
||||
|
||||
const dateLine = moment().tz(tasksRequest?.tasks[0].bodyshop.timezone).format("M/DD/YYYY @ hh:mm a");
|
||||
|
||||
// There is only the one email to send to this author.
|
||||
if (recipient.count === 1) {
|
||||
const onlyTask = groupedTasks[recipient.email][0];
|
||||
@@ -272,7 +267,8 @@ const tasksRemindEmail = async (req, res) => {
|
||||
onlyTask.due_date,
|
||||
onlyTask.bodyshop,
|
||||
onlyTask.job,
|
||||
onlyTask.id
|
||||
onlyTask.id,
|
||||
dateLine
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -295,6 +291,7 @@ const tasksRemindEmail = async (req, res) => {
|
||||
emailData.html = generateEmailTemplate({
|
||||
header: `${allTasks.length} Tasks require your attention`,
|
||||
subHeader: `Please click on the Tasks below to view the Task.`,
|
||||
dateLine,
|
||||
body: `<ul>
|
||||
${allTasks
|
||||
.map((task) =>
|
||||
@@ -333,6 +330,29 @@ const tasksRemindEmail = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Note: Uncomment this to test locally, it will call the remind_at email check every 20 seconds
|
||||
// const callTaskRemindEmailInternally = () => {
|
||||
// const req = {
|
||||
// body: {
|
||||
// // You can mock any request data here if needed
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// const res = {
|
||||
// status: (code) => {
|
||||
// return {
|
||||
// json: (data) => {
|
||||
// console.log(`Response Status: ${code}`, data);
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// // Call the taskRemindEmail function with mock req and res
|
||||
// tasksRemindEmail(req, res);
|
||||
// };
|
||||
// setInterval(callTaskRemindEmailInternally, 20000);
|
||||
|
||||
module.exports = {
|
||||
taskAssignedEmail,
|
||||
tasksRemindEmail,
|
||||
|
||||
@@ -2489,6 +2489,7 @@ exports.QUERY_REMIND_TASKS = `
|
||||
bodyshop {
|
||||
shopname
|
||||
convenient_company
|
||||
timezone
|
||||
}
|
||||
bodyshopid
|
||||
}
|
||||
@@ -2512,6 +2513,7 @@ query QUERY_TASK_BY_ID($id: uuid!) {
|
||||
bodyshop{
|
||||
shopname
|
||||
convenient_company
|
||||
timezone
|
||||
}
|
||||
job{
|
||||
ro_number
|
||||
|
||||
Reference in New Issue
Block a user