@@ -51,6 +51,12 @@ app.use(bodyParser.json({limit: "50mb"}));
|
|||||||
app.use(bodyParser.urlencoded({limit: "50mb", extended: true}));
|
app.use(bodyParser.urlencoded({limit: "50mb", extended: true}));
|
||||||
app.use(cors({credentials: true, exposedHeaders: ["set-cookie"]}));
|
app.use(cors({credentials: true, exposedHeaders: ["set-cookie"]}));
|
||||||
|
|
||||||
|
// Helper middleware
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
req.logger = logger;
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
// Route groupings
|
// Route groupings
|
||||||
app.use('/', require("./server/routes/miscellaneousRoutes"));
|
app.use('/', require("./server/routes/miscellaneousRoutes"));
|
||||||
app.use("/notifications", require("./server/routes/notificationsRoutes"));
|
app.use("/notifications", require("./server/routes/notificationsRoutes"));
|
||||||
@@ -85,4 +91,4 @@ main()
|
|||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
logger.log(`[${process.env.NODE_ENV || "DEVELOPMENT"}] Server failed to start on port ${port}`, "ERROR", "api", error);
|
logger.log(`[${process.env.NODE_ENV || "DEVELOPMENT"}] Server failed to start on port ${port}`, "ERROR", "api", error);
|
||||||
});
|
});
|
||||||
@@ -1,269 +1,269 @@
|
|||||||
const path = require("path");
|
const path = require("path");
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
`.env.${process.env.NODE_ENV || "development"}`
|
`.env.${process.env.NODE_ENV || "development"}`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
let nodemailer = require("nodemailer");
|
let nodemailer = require("nodemailer");
|
||||||
let aws = require("@aws-sdk/client-ses");
|
let aws = require("@aws-sdk/client-ses");
|
||||||
let { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
let {defaultProvider} = require("@aws-sdk/credential-provider-node");
|
||||||
|
|
||||||
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 ses = new aws.SES({
|
const ses = new aws.SES({
|
||||||
// The key apiVersion is no longer supported in v3, and can be removed.
|
// The key apiVersion is no longer supported in v3, and can be removed.
|
||||||
// @deprecated The client uses the "latest" apiVersion.
|
// @deprecated The client uses the "latest" apiVersion.
|
||||||
apiVersion: "latest",
|
apiVersion: "latest",
|
||||||
region: "ca-central-1",
|
region: "ca-central-1",
|
||||||
defaultProvider
|
defaultProvider
|
||||||
});
|
});
|
||||||
|
|
||||||
let transporter = nodemailer.createTransport({
|
let transporter = nodemailer.createTransport({
|
||||||
SES: { ses, aws },
|
SES: {ses, aws},
|
||||||
});
|
});
|
||||||
|
|
||||||
exports.sendServerEmail = async function ({ subject, text }) {
|
exports.sendServerEmail = async function ({subject, text}) {
|
||||||
if (process.env.NODE_ENV === undefined) return;
|
if (process.env.NODE_ENV === undefined) return;
|
||||||
try {
|
try {
|
||||||
transporter.sendMail(
|
transporter.sendMail(
|
||||||
{
|
|
||||||
from: `ImEX Online API - ${process.env.NODE_ENV} <noreply@imex.online>`,
|
|
||||||
to: ["patrick@imexsystems.ca", "support@thinkimex.com"],
|
|
||||||
subject: subject,
|
|
||||||
text: text,
|
|
||||||
ses: {
|
|
||||||
// optional extra arguments for SendRawEmail
|
|
||||||
Tags: [
|
|
||||||
{
|
{
|
||||||
Name: "tag_name",
|
from: `ImEX Online API - ${process.env.NODE_ENV} <noreply@imex.online>`,
|
||||||
Value: "tag_value",
|
to: ["patrick@imexsystems.ca", "support@thinkimex.com"],
|
||||||
|
subject: subject,
|
||||||
|
text: text,
|
||||||
|
ses: {
|
||||||
|
// optional extra arguments for SendRawEmail
|
||||||
|
Tags: [
|
||||||
|
{
|
||||||
|
Name: "tag_name",
|
||||||
|
Value: "tag_value",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
(err, info) => {
|
||||||
},
|
console.log(err || info);
|
||||||
},
|
}
|
||||||
(err, info) => {
|
);
|
||||||
console.log(err || info);
|
} catch (error) {
|
||||||
}
|
console.log(error);
|
||||||
);
|
logger.log("server-email-failure", "error", null, null, error);
|
||||||
} catch (error) {
|
}
|
||||||
console.log(error);
|
|
||||||
logger.log("server-email-failure", "error", null, null, error);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
exports.sendTaskEmail = async function ({ to, subject, text, attachments }) {
|
exports.sendTaskEmail = async function ({to, subject, text, attachments}) {
|
||||||
try {
|
try {
|
||||||
transporter.sendMail(
|
transporter.sendMail(
|
||||||
{
|
{
|
||||||
from: `ImEX Online <noreply@imex.online>`,
|
from: `ImEX Online <noreply@imex.online>`,
|
||||||
to: to,
|
to: to,
|
||||||
subject: subject,
|
subject: subject,
|
||||||
text: text,
|
text: text,
|
||||||
attachments: attachments || null,
|
attachments: attachments || null,
|
||||||
},
|
},
|
||||||
(err, info) => {
|
(err, info) => {
|
||||||
console.log(err || info);
|
console.log(err || info);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.log(error);
|
||||||
logger.log("server-email-failure", "error", null, null, error);
|
logger.log("server-email-failure", "error", null, null, error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.sendEmail = async (req, res) => {
|
exports.sendEmail = async (req, res) => {
|
||||||
logger.log("send-email", "DEBUG", req.user.email, null, {
|
logger.log("send-email", "DEBUG", req.user.email, null, {
|
||||||
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,
|
||||||
to: req.body.to,
|
to: req.body.to,
|
||||||
cc: req.body.cc,
|
cc: req.body.cc,
|
||||||
subject: req.body.subject,
|
subject: req.body.subject,
|
||||||
});
|
});
|
||||||
|
|
||||||
let downloadedMedia = [];
|
let downloadedMedia = [];
|
||||||
if (req.body.media && req.body.media.length > 0) {
|
if (req.body.media && req.body.media.length > 0) {
|
||||||
downloadedMedia = await Promise.all(
|
downloadedMedia = await Promise.all(
|
||||||
req.body.media.map((m) => {
|
req.body.media.map((m) => {
|
||||||
try {
|
try {
|
||||||
return getImage(m);
|
return getImage(m);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("send-email-error", "ERROR", req.user.email, null, {
|
logger.log("send-email-error", "ERROR", req.user.email, null, {
|
||||||
|
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||||
|
replyTo: req.body.ReplyTo.Email,
|
||||||
|
to: req.body.to,
|
||||||
|
cc: req.body.cc,
|
||||||
|
subject: req.body.subject,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
transporter.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,
|
||||||
to: req.body.to,
|
to: req.body.to,
|
||||||
cc: req.body.cc,
|
cc: req.body.cc,
|
||||||
subject: req.body.subject,
|
subject: req.body.subject,
|
||||||
error,
|
attachments:
|
||||||
});
|
[
|
||||||
|
...((req.body.attachments &&
|
||||||
|
req.body.attachments.map((a) => {
|
||||||
|
return {
|
||||||
|
filename: a.filename,
|
||||||
|
path: a.path,
|
||||||
|
};
|
||||||
|
})) ||
|
||||||
|
[]),
|
||||||
|
...downloadedMedia.map((a) => {
|
||||||
|
return {
|
||||||
|
path: a,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
] || null,
|
||||||
|
html: req.body.html,
|
||||||
|
ses: {
|
||||||
|
// optional extra arguments for SendRawEmail
|
||||||
|
Tags: [
|
||||||
|
{
|
||||||
|
Name: "tag_name",
|
||||||
|
Value: "tag_value",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(err, info) => {
|
||||||
|
console.log(err || info);
|
||||||
|
if (info) {
|
||||||
|
logger.log("send-email-success", "DEBUG", req.user.email, null, {
|
||||||
|
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||||
|
replyTo: req.body.ReplyTo.Email,
|
||||||
|
to: req.body.to,
|
||||||
|
cc: req.body.cc,
|
||||||
|
subject: req.body.subject,
|
||||||
|
// info,
|
||||||
|
});
|
||||||
|
logEmail(req, {
|
||||||
|
to: req.body.to,
|
||||||
|
cc: req.body.cc,
|
||||||
|
subject: req.body.subject,
|
||||||
|
messageId: info.response,
|
||||||
|
});
|
||||||
|
res.json({
|
||||||
|
success: true, //response: info
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
logger.log("send-email-failure", "ERROR", req.user.email, null, {
|
||||||
|
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||||
|
replyTo: req.body.ReplyTo.Email,
|
||||||
|
to: req.body.to,
|
||||||
|
cc: req.body.cc,
|
||||||
|
subject: req.body.subject,
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
logEmail(req, {
|
||||||
|
to: req.body.to,
|
||||||
|
cc: req.body.cc,
|
||||||
|
subject: req.body.subject,
|
||||||
|
bodyshopid: req.body.bodyshopid,
|
||||||
|
});
|
||||||
|
res.status(500).json({success: false, error: err});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
transporter.sendMail(
|
|
||||||
{
|
|
||||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
|
||||||
replyTo: req.body.ReplyTo.Email,
|
|
||||||
to: req.body.to,
|
|
||||||
cc: req.body.cc,
|
|
||||||
subject: req.body.subject,
|
|
||||||
attachments:
|
|
||||||
[
|
|
||||||
...((req.body.attachments &&
|
|
||||||
req.body.attachments.map((a) => {
|
|
||||||
return {
|
|
||||||
filename: a.filename,
|
|
||||||
path: a.path,
|
|
||||||
};
|
|
||||||
})) ||
|
|
||||||
[]),
|
|
||||||
...downloadedMedia.map((a) => {
|
|
||||||
return {
|
|
||||||
path: a,
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
] || null,
|
|
||||||
html: req.body.html,
|
|
||||||
ses: {
|
|
||||||
// optional extra arguments for SendRawEmail
|
|
||||||
Tags: [
|
|
||||||
{
|
|
||||||
Name: "tag_name",
|
|
||||||
Value: "tag_value",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
(err, info) => {
|
|
||||||
console.log(err || info);
|
|
||||||
if (info) {
|
|
||||||
logger.log("send-email-success", "DEBUG", req.user.email, null, {
|
|
||||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
|
||||||
replyTo: req.body.ReplyTo.Email,
|
|
||||||
to: req.body.to,
|
|
||||||
cc: req.body.cc,
|
|
||||||
subject: req.body.subject,
|
|
||||||
// info,
|
|
||||||
});
|
|
||||||
logEmail(req, {
|
|
||||||
to: req.body.to,
|
|
||||||
cc: req.body.cc,
|
|
||||||
subject: req.body.subject,
|
|
||||||
messageId: info.response,
|
|
||||||
});
|
|
||||||
res.json({
|
|
||||||
success: true, //response: info
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
logger.log("send-email-failure", "ERROR", req.user.email, null, {
|
|
||||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
|
||||||
replyTo: req.body.ReplyTo.Email,
|
|
||||||
to: req.body.to,
|
|
||||||
cc: req.body.cc,
|
|
||||||
subject: req.body.subject,
|
|
||||||
error: err,
|
|
||||||
});
|
|
||||||
logEmail(req, {
|
|
||||||
to: req.body.to,
|
|
||||||
cc: req.body.cc,
|
|
||||||
subject: req.body.subject,
|
|
||||||
bodyshopid: req.body.bodyshopid,
|
|
||||||
});
|
|
||||||
res.status(500).json({ success: false, error: err });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getImage(imageUrl) {
|
async function getImage(imageUrl) {
|
||||||
let image = await axios.get(imageUrl, { responseType: "arraybuffer" });
|
let image = await axios.get(imageUrl, {responseType: "arraybuffer"});
|
||||||
let raw = Buffer.from(image.data).toString("base64");
|
let raw = Buffer.from(image.data).toString("base64");
|
||||||
return "data:" + image.headers["content-type"] + ";base64," + raw;
|
return "data:" + image.headers["content-type"] + ";base64," + raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function logEmail(req, email) {
|
async function logEmail(req, email) {
|
||||||
try {
|
try {
|
||||||
const insertresult = await client.request(queries.INSERT_EMAIL_AUDIT, {
|
const insertresult = await client.request(queries.INSERT_EMAIL_AUDIT, {
|
||||||
email: {
|
email: {
|
||||||
to: email.to,
|
to: email.to,
|
||||||
cc: email.cc,
|
cc: email.cc,
|
||||||
subject: email.subject,
|
subject: email.subject,
|
||||||
bodyshopid: req.body.bodyshopid,
|
bodyshopid: req.body.bodyshopid,
|
||||||
useremail: req.user.email,
|
useremail: req.user.email,
|
||||||
contents: req.body.html,
|
contents: req.body.html,
|
||||||
jobid: req.body.jobid,
|
jobid: req.body.jobid,
|
||||||
sesmessageid: email.messageId,
|
sesmessageid: email.messageId,
|
||||||
status: "Sent",
|
status: "Sent",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
console.log(insertresult);
|
console.log(insertresult);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("email-log-error", "error", req.user.email, null, {
|
logger.log("email-log-error", "error", req.user.email, null, {
|
||||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||||
to: req.body.to,
|
to: req.body.to,
|
||||||
cc: req.body.cc,
|
cc: req.body.cc,
|
||||||
subject: req.body.subject,
|
subject: req.body.subject,
|
||||||
// info,
|
// info,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.emailBounce = async function (req, res, next) {
|
exports.emailBounce = async function (req, res) {
|
||||||
try {
|
try {
|
||||||
const body = JSON.parse(req.body);
|
const body = JSON.parse(req.body);
|
||||||
if (body.Type === "SubscriptionConfirmation") {
|
if (body.Type === "SubscriptionConfirmation") {
|
||||||
logger.log("SNS-message", "DEBUG", "api", null, {
|
logger.log("SNS-message", "DEBUG", "api", null, {
|
||||||
body: req.body,
|
body: req.body,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
const message = JSON.parse(body.Message);
|
|
||||||
if (message.notificationType === "Bounce") {
|
|
||||||
let replyTo, subject, messageId;
|
|
||||||
message.mail.headers.forEach((header) => {
|
|
||||||
if (header.name === "Reply-To") {
|
|
||||||
replyTo = header.value;
|
|
||||||
} else if (header.name === "Subject") {
|
|
||||||
subject = header.value;
|
|
||||||
}
|
}
|
||||||
});
|
const message = JSON.parse(body.Message);
|
||||||
messageId = message.mail.messageId;
|
if (message.notificationType === "Bounce") {
|
||||||
if (replyTo === "noreply@imex.online") {
|
let replyTo, subject, messageId;
|
||||||
res.sendStatus(200);
|
message.mail.headers.forEach((header) => {
|
||||||
return;
|
if (header.name === "Reply-To") {
|
||||||
}
|
replyTo = header.value;
|
||||||
//If it's bounced, log it as bounced in audit log. Send an email to the user.
|
} else if (header.name === "Subject") {
|
||||||
const result = await client.request(queries.UPDATE_EMAIL_AUDIT, {
|
subject = header.value;
|
||||||
sesid: messageId,
|
}
|
||||||
status: "Bounced",
|
});
|
||||||
context: message.bounce?.bouncedRecipients,
|
messageId = message.mail.messageId;
|
||||||
});
|
if (replyTo === "noreply@imex.online") {
|
||||||
transporter.sendMail(
|
res.sendStatus(200);
|
||||||
{
|
return;
|
||||||
from: `ImEX Online <noreply@imex.online>`,
|
}
|
||||||
to: replyTo,
|
//If it's bounced, log it as bounced in audit log. Send an email to the user.
|
||||||
//bcc: "patrick@snapt.ca",
|
const result = await client.request(queries.UPDATE_EMAIL_AUDIT, {
|
||||||
subject: `ImEX Online Bounced Email - RE: ${subject}`,
|
sesid: messageId,
|
||||||
text: `ImEX Online has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error.
|
status: "Bounced",
|
||||||
|
context: message.bounce?.bouncedRecipients,
|
||||||
|
});
|
||||||
|
transporter.sendMail(
|
||||||
|
{
|
||||||
|
from: `ImEX Online <noreply@imex.online>`,
|
||||||
|
to: replyTo,
|
||||||
|
//bcc: "patrick@snapt.ca",
|
||||||
|
subject: `ImEX Online Bounced Email - RE: ${subject}`,
|
||||||
|
text: `ImEX Online has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error.
|
||||||
|
|
||||||
${body.bounce?.bouncedRecipients.map(
|
${body.bounce?.bouncedRecipients.map(
|
||||||
(r) =>
|
(r) =>
|
||||||
`Recipient: ${r.emailAddress} | Status: ${r.action} | Code: ${r.diagnosticCode}
|
`Recipient: ${r.emailAddress} | Status: ${r.action} | Code: ${r.diagnosticCode}
|
||||||
`
|
`
|
||||||
)}
|
)}
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
(err, info) => {
|
(err, info) => {
|
||||||
console.log("***", err || info);
|
console.log("***", err || info);
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
);
|
} catch (error) {
|
||||||
|
logger.log("sns-error", "ERROR", "api", null, {
|
||||||
|
error: JSON.stringify(error),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
res.sendStatus(200);
|
||||||
logger.log("sns-error", "ERROR", "api", null, {
|
|
||||||
error: JSON.stringify(error),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
res.sendStatus(200);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,287 +1,215 @@
|
|||||||
var admin = require("firebase-admin");
|
const admin = require("firebase-admin");
|
||||||
const logger = require("../utils/logger");
|
const logger = require("../utils/logger");
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const { auth } = require("firebase-admin");
|
const {auth} = require("firebase-admin");
|
||||||
|
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
`.env.${process.env.NODE_ENV || "development"}`
|
`.env.${process.env.NODE_ENV || "development"}`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
const client = require("../graphql-client/graphql-client").client;
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
var serviceAccount = require(process.env.FIREBASE_ADMINSDK_JSON);
|
|
||||||
|
const serviceAccount = require(process.env.FIREBASE_ADMINSDK_JSON);
|
||||||
|
const adminEmail = require("../utils/adminEmail");
|
||||||
|
|
||||||
admin.initializeApp({
|
admin.initializeApp({
|
||||||
credential: admin.credential.cert(serviceAccount),
|
credential: admin.credential.cert(serviceAccount),
|
||||||
databaseURL: process.env.FIREBASE_DATABASE_URL,
|
databaseURL: process.env.FIREBASE_DATABASE_URL,
|
||||||
});
|
});
|
||||||
|
|
||||||
exports.admin = admin;
|
exports.admin = admin;
|
||||||
|
|
||||||
const adminEmail = [
|
|
||||||
"patrick@imex.dev",
|
|
||||||
//"patrick@imex.test",
|
|
||||||
"patrick@imex.prod",
|
|
||||||
"patrick@imexsystems.ca",
|
|
||||||
"patrick@thinkimex.com",
|
|
||||||
];
|
|
||||||
|
|
||||||
exports.createUser = async (req, res) => {
|
exports.createUser = async (req, res) => {
|
||||||
logger.log("admin-create-user", "ADMIN", req.user.email, null, {
|
logger.log("admin-create-user", "ADMIN", req.user.email, null, {
|
||||||
request: req.body,
|
request: req.body,
|
||||||
ioadmin: true,
|
ioadmin: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { email, displayName, password, shopid, authlevel } = req.body;
|
const {email, displayName, password, shopid, authlevel} = req.body;
|
||||||
try {
|
try {
|
||||||
const userRecord = await admin
|
const userRecord = await admin
|
||||||
.auth()
|
.auth()
|
||||||
.createUser({ email, displayName, password });
|
.createUser({email, displayName, password});
|
||||||
|
|
||||||
// See the UserRecord reference doc for the contents of userRecord.
|
// See the UserRecord reference doc for the contents of userRecord.
|
||||||
|
|
||||||
const result = await client.request(
|
const result = await client.request(
|
||||||
`
|
`
|
||||||
mutation INSERT_USER($user: users_insert_input!) {
|
mutation INSERT_USER($user: users_insert_input!) {
|
||||||
insert_users_one(object: $user) {
|
insert_users_one(object: $user) {
|
||||||
email
|
email
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
{
|
{
|
||||||
user: {
|
user: {
|
||||||
email: email.toLowerCase(),
|
email: email.toLowerCase(),
|
||||||
authid: userRecord.uid,
|
authid: userRecord.uid,
|
||||||
associations: {
|
associations: {
|
||||||
data: [{ shopid, authlevel, active: true }],
|
data: [{shopid, authlevel, active: true}],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
res.json({ userRecord, result });
|
res.json({userRecord, result});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("admin-update-user-error", "ERROR", req.user.email, null, {
|
logger.log("admin-update-user-error", "ERROR", req.user.email, null, {
|
||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
res.status(500).json(error);
|
res.status(500).json(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.updateUser = (req, res) => {
|
exports.updateUser = (req, res) => {
|
||||||
logger.log("admin-update-user", "ADMIN", req.user.email, null, {
|
logger.log("admin-update-user", "ADMIN", req.user.email, null, {
|
||||||
request: req.body,
|
|
||||||
ioadmin: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
|
||||||
logger.log(
|
|
||||||
"admin-update-user-unauthorized",
|
|
||||||
"ERROR",
|
|
||||||
req.user.email,
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
request: req.body,
|
request: req.body,
|
||||||
user: req.user,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
res.sendStatus(404);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
admin
|
|
||||||
.auth()
|
|
||||||
.updateUser(
|
|
||||||
req.body.uid,
|
|
||||||
req.body.user
|
|
||||||
// {
|
|
||||||
// email: "modifiedUser@example.com",
|
|
||||||
// phoneNumber: "+11234567890",
|
|
||||||
// emailVerified: true,
|
|
||||||
// password: "newPassword",
|
|
||||||
// displayName: "Jane Doe",
|
|
||||||
// photoURL: "http://www.example.com/12345678/photo.png",
|
|
||||||
// disabled: true,
|
|
||||||
// }
|
|
||||||
)
|
|
||||||
.then((userRecord) => {
|
|
||||||
// See the UserRecord reference doc for the contents of userRecord.
|
|
||||||
|
|
||||||
logger.log("admin-update-user-success", "ADMIN", req.user.email, null, {
|
|
||||||
userRecord,
|
|
||||||
ioadmin: true,
|
ioadmin: true,
|
||||||
});
|
|
||||||
res.json(userRecord);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.log("admin-update-user-error", "ERROR", req.user.email, null, {
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
res.status(500).json(error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
||||||
|
logger.log(
|
||||||
|
"admin-update-user-unauthorized",
|
||||||
|
"ERROR",
|
||||||
|
req.user.email,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
request: req.body,
|
||||||
|
user: req.user,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
res.sendStatus(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
admin
|
||||||
|
.auth()
|
||||||
|
.updateUser(
|
||||||
|
req.body.uid,
|
||||||
|
req.body.user
|
||||||
|
// {
|
||||||
|
// email: "modifiedUser@example.com",
|
||||||
|
// phoneNumber: "+11234567890",
|
||||||
|
// emailVerified: true,
|
||||||
|
// password: "newPassword",
|
||||||
|
// displayName: "Jane Doe",
|
||||||
|
// photoURL: "http://www.example.com/12345678/photo.png",
|
||||||
|
// disabled: true,
|
||||||
|
// }
|
||||||
|
)
|
||||||
|
.then((userRecord) => {
|
||||||
|
// See the UserRecord reference doc for the contents of userRecord.
|
||||||
|
|
||||||
|
logger.log("admin-update-user-success", "ADMIN", req.user.email, null, {
|
||||||
|
userRecord,
|
||||||
|
ioadmin: true,
|
||||||
|
});
|
||||||
|
res.json(userRecord);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.log("admin-update-user-error", "ERROR", req.user.email, null, {
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
res.status(500).json(error);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.getUser = (req, res) => {
|
exports.getUser = (req, res) => {
|
||||||
logger.log("admin-get-user", "ADMIN", req.user.email, null, {
|
logger.log("admin-get-user", "ADMIN", req.user.email, null, {
|
||||||
request: req.body,
|
|
||||||
ioadmin: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
|
||||||
logger.log(
|
|
||||||
"admin-update-user-unauthorized",
|
|
||||||
"ERROR",
|
|
||||||
req.user.email,
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
request: req.body,
|
request: req.body,
|
||||||
user: req.user,
|
ioadmin: true,
|
||||||
}
|
|
||||||
);
|
|
||||||
res.sendStatus(404);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
admin
|
|
||||||
.auth()
|
|
||||||
.getUser(req.body.uid)
|
|
||||||
.then((userRecord) => {
|
|
||||||
res.json(userRecord);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
logger.log("admin-get-user-error", "ERROR", req.user.email, null, {
|
|
||||||
error,
|
|
||||||
});
|
|
||||||
res.status(500).json(error);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
||||||
|
logger.log(
|
||||||
|
"admin-update-user-unauthorized",
|
||||||
|
"ERROR",
|
||||||
|
req.user.email,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
request: req.body,
|
||||||
|
user: req.user,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
res.sendStatus(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
admin
|
||||||
|
.auth()
|
||||||
|
.getUser(req.body.uid)
|
||||||
|
.then((userRecord) => {
|
||||||
|
res.json(userRecord);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
logger.log("admin-get-user-error", "ERROR", req.user.email, null, {
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
res.status(500).json(error);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.sendNotification = async (req, res) => {
|
exports.sendNotification = async (req, res) => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Send a message to the device corresponding to the provided
|
// Send a message to the device corresponding to the provided
|
||||||
// registration token.
|
// registration token.
|
||||||
admin
|
admin
|
||||||
.messaging()
|
.messaging()
|
||||||
.send({
|
.send({
|
||||||
topic: "PRD_PATRICK-messaging",
|
topic: "PRD_PATRICK-messaging",
|
||||||
notification: {
|
notification: {
|
||||||
title: `ImEX Online Message - +16049992002`,
|
title: `ImEX Online Message - +16049992002`,
|
||||||
body: "Test Noti.",
|
body: "Test Noti.",
|
||||||
//imageUrl: "https://thinkimex.com/img/io-fcm.png",
|
//imageUrl: "https://thinkimex.com/img/io-fcm.png",
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
type: "messaging-inbound",
|
type: "messaging-inbound",
|
||||||
conversationid: "e0eb17c3-3a78-4e3f-b932-55ef35aa2297",
|
conversationid: "e0eb17c3-3a78-4e3f-b932-55ef35aa2297",
|
||||||
text: "Hello. ",
|
text: "Hello. ",
|
||||||
image_path: "",
|
image_path: "",
|
||||||
phone_num: "+16049992002",
|
phone_num: "+16049992002",
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
// Response is a message ID string.
|
// Response is a message ID string.
|
||||||
console.log("Successfully sent message:", response);
|
console.log("Successfully sent message:", response);
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.log("Error sending message:", error);
|
console.log("Error sending message:", error);
|
||||||
});
|
});
|
||||||
|
|
||||||
res.sendStatus(200);
|
res.sendStatus(200);
|
||||||
}, 500);
|
}, 500);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.subscribe = async (req, res) => {
|
exports.subscribe = async (req, res) => {
|
||||||
const result = await admin
|
const result = await admin
|
||||||
.messaging()
|
.messaging()
|
||||||
.subscribeToTopic(
|
.subscribeToTopic(
|
||||||
req.body.fcm_tokens,
|
req.body.fcm_tokens,
|
||||||
`${req.body.imexshopid}-${req.body.type}`
|
`${req.body.imexshopid}-${req.body.type}`
|
||||||
);
|
);
|
||||||
|
|
||||||
res.json(result);
|
res.json(result);
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.unsubscribe = async (req, res) => {
|
exports.unsubscribe = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const result = await admin
|
const result = await admin
|
||||||
.messaging()
|
.messaging()
|
||||||
.unsubscribeFromTopic(
|
.unsubscribeFromTopic(
|
||||||
req.body.fcm_tokens,
|
req.body.fcm_tokens,
|
||||||
`${req.body.imexshopid}-${req.body.type}`
|
`${req.body.imexshopid}-${req.body.type}`
|
||||||
);
|
);
|
||||||
|
|
||||||
res.json(result);
|
res.json(result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
res.sendStatus(500);
|
res.sendStatus(500);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.validateFirebaseIdToken = async (req, res, next) => {
|
|
||||||
if (
|
|
||||||
(!req.headers.authorization ||
|
|
||||||
!req.headers.authorization.startsWith("Bearer ")) &&
|
|
||||||
!(req.cookies && req.cookies.__session)
|
|
||||||
) {
|
|
||||||
console.error("Unauthorized attempt. No authorization provided.");
|
|
||||||
res.status(403).send("Unauthorized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let idToken;
|
|
||||||
if (
|
|
||||||
req.headers.authorization &&
|
|
||||||
req.headers.authorization.startsWith("Bearer ")
|
|
||||||
) {
|
|
||||||
// console.log('Found "Authorization" header');
|
|
||||||
// Read the ID Token from the Authorization header.
|
|
||||||
idToken = req.headers.authorization.split("Bearer ")[1];
|
|
||||||
} else if (req.cookies) {
|
|
||||||
//console.log('Found "__session" cookie');
|
|
||||||
// Read the ID Token from cookie.
|
|
||||||
idToken = req.cookies.__session;
|
|
||||||
} else {
|
|
||||||
// No cookie
|
|
||||||
console.error("Unauthorized attempt. No cookie provided.");
|
|
||||||
logger.log("api-unauthorized-call", "WARN", null, null, {
|
|
||||||
req,
|
|
||||||
type: "no-cookie",
|
|
||||||
});
|
|
||||||
res.status(403).send("Unauthorized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
|
|
||||||
//console.log("ID Token correctly decoded", decodedIdToken);
|
|
||||||
req.user = decodedIdToken;
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
} catch (error) {
|
|
||||||
logger.log("api-unauthorized-call", "WARN", null, null, {
|
|
||||||
path: req.path,
|
|
||||||
body: req.body,
|
|
||||||
|
|
||||||
type: "unauthroized",
|
|
||||||
...error,
|
|
||||||
});
|
|
||||||
|
|
||||||
res.status(401).send("Unauthorized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.validateAdmin = async (req, res, next) => {
|
|
||||||
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
|
||||||
logger.log("admin-validation-failed", "ERROR", req.user.email, null, {
|
|
||||||
request: req.body,
|
|
||||||
user: req.user,
|
|
||||||
});
|
|
||||||
res.sendStatus(404);
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//Admin claims code.
|
//Admin claims code.
|
||||||
// const uid = "JEqqYlsadwPEXIiyRBR55fflfko1";
|
// const uid = "JEqqYlsadwPEXIiyRBR55fflfko1";
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ async function JobCosting(req, res) {
|
|||||||
const { jobid } = req.body;
|
const { jobid } = req.body;
|
||||||
|
|
||||||
const BearerToken = req.headers.authorization;
|
const BearerToken = req.headers.authorization;
|
||||||
|
|
||||||
logger.log("job-costing-start", "DEBUG", req.user.email, jobid, null);
|
logger.log("job-costing-start", "DEBUG", req.user.email, jobid, null);
|
||||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||||
headers: {
|
headers: {
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
|
const _ = require("lodash");
|
||||||
const jobLifecycle = (req, res) => {
|
const jobLifecycle = (req, res) => {
|
||||||
return res.status(200).send("jobLifecycle");
|
const {jobids} = req.body;
|
||||||
|
return _.isArray(jobids) ?
|
||||||
|
handleMultipleJobs(jobids, req, res) :
|
||||||
|
handleSingleJob(jobids, req, res);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleMultipleJobs = (jobIDs, req, res) => {
|
||||||
|
return res.status(200).send(jobIDs);
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleSingleJob = (req, res) => {
|
||||||
|
return res.status(200).send(req.body);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = jobLifecycle;
|
module.exports = jobLifecycle;
|
||||||
@@ -9,89 +9,84 @@ const logger = require("../utils/logger");
|
|||||||
Dinero.globalRoundingMode = "HALF_EVEN";
|
Dinero.globalRoundingMode = "HALF_EVEN";
|
||||||
const path = require("path");
|
const path = require("path");
|
||||||
const client = require("../graphql-client/graphql-client").client;
|
const client = require("../graphql-client/graphql-client").client;
|
||||||
|
|
||||||
require("dotenv").config({
|
require("dotenv").config({
|
||||||
path: path.resolve(
|
path: path.resolve(
|
||||||
process.cwd(),
|
process.cwd(),
|
||||||
`.env.${process.env.NODE_ENV || "development"}`
|
`.env.${process.env.NODE_ENV || "development"}`
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
async function StatusTransition(req, res) {
|
async function StatusTransition(req, res) {
|
||||||
if (req.headers["event-secret"] !== process.env.EVENT_SECRET) {
|
const {
|
||||||
res.status(401).send("Unauthorized");
|
id: jobid,
|
||||||
return;
|
status: value,
|
||||||
}
|
shopid: bodyshopid,
|
||||||
|
} = req.body.event.data.new;
|
||||||
// return res.sendStatus(200);
|
|
||||||
|
|
||||||
const {
|
|
||||||
id: jobid,
|
|
||||||
status: value,
|
|
||||||
shopid: bodyshopid,
|
|
||||||
} = req.body.event.data.new;
|
|
||||||
|
|
||||||
// Create record OPEN on new item, enter state
|
// Create record OPEN on new item, enter state
|
||||||
// If change to SCHEDULE, update the last record and create a new record (update status and end time on old record, create a new record saying we came from previous status going to previous status
|
// If change to SCHEDULE, update the last record and create a new record (update status and end time on old record, create a new record saying we came from previous status going to previous status
|
||||||
// (Timeline)
|
// (Timeline)
|
||||||
// Final status is exported, there is no end date as there is no further transition (has no end date)
|
// Final status is exported, there is no end date as there is no further transition (has no end date)
|
||||||
try {
|
try {
|
||||||
const { update_transitions } = await client.request(
|
const {update_transitions} = await client.request(
|
||||||
queries.UPDATE_OLD_TRANSITION,
|
queries.UPDATE_OLD_TRANSITION,
|
||||||
{
|
{
|
||||||
jobid: jobid,
|
jobid: jobid,
|
||||||
existingTransition: {
|
existingTransition: {
|
||||||
end: new Date(),
|
end: new Date(),
|
||||||
next_value: value,
|
next_value: value,
|
||||||
|
|
||||||
//duration
|
//duration
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
let duration =
|
let duration =
|
||||||
update_transitions.affected_rows === 0
|
update_transitions.affected_rows === 0
|
||||||
? 0
|
? 0
|
||||||
: new Date(update_transitions.returning[0].end) -
|
: new Date(update_transitions.returning[0].end) -
|
||||||
new Date(update_transitions.returning[0].start);
|
new Date(update_transitions.returning[0].start);
|
||||||
|
|
||||||
const resp2 = await client.request(queries.INSERT_NEW_TRANSITION, {
|
const resp2 = await client.request(queries.INSERT_NEW_TRANSITION, {
|
||||||
oldTransitionId:
|
oldTransitionId:
|
||||||
update_transitions.affected_rows === 0
|
update_transitions.affected_rows === 0
|
||||||
? null
|
? null
|
||||||
: update_transitions.returning[0].id,
|
: update_transitions.returning[0].id,
|
||||||
duration,
|
duration,
|
||||||
newTransition: {
|
newTransition: {
|
||||||
bodyshopid: bodyshopid,
|
bodyshopid: bodyshopid,
|
||||||
jobid: jobid,
|
jobid: jobid,
|
||||||
start:
|
start:
|
||||||
update_transitions.affected_rows === 0
|
update_transitions.affected_rows === 0
|
||||||
? new Date()
|
? new Date()
|
||||||
: update_transitions.returning[0].end,
|
: update_transitions.returning[0].end,
|
||||||
prev_value:
|
prev_value:
|
||||||
update_transitions.affected_rows === 0
|
update_transitions.affected_rows === 0
|
||||||
? null
|
? null
|
||||||
: update_transitions.returning[0].value,
|
: update_transitions.returning[0].value,
|
||||||
value: value,
|
value: value,
|
||||||
type: "status",
|
type: "status",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//Check to see if there is an existing status transition record.
|
//Check to see if there is an existing status transition record.
|
||||||
//Query using Job ID, start is not null, end is null.
|
//Query using Job ID, start is not null, end is null.
|
||||||
|
|
||||||
//If there is no existing record, this is the start of the transition life cycle.
|
//If there is no existing record, this is the start of the transition life cycle.
|
||||||
// Create the initial transition record.
|
// Create the initial transition record.
|
||||||
|
|
||||||
//If there is a current status transition record, update it with the end date, duration, and next value.
|
//If there is a current status transition record, update it with the end date, duration, and next value.
|
||||||
|
|
||||||
res.sendStatus(200); //.json(ret);
|
res.sendStatus(200); //.json(ret);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.log("job-status-transition-error", "ERROR", req.user?.email, jobid, {
|
logger.log("job-status-transition-error", "ERROR", req.user?.email, jobid, {
|
||||||
message: error.message,
|
message: error.message,
|
||||||
stack: error.stack,
|
stack: error.stack,
|
||||||
});
|
});
|
||||||
|
|
||||||
res.status(400).send(JSON.stringify(error));
|
res.status(400).send(JSON.stringify(error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.statustransition = StatusTransition;
|
exports.statustransition = StatusTransition;
|
||||||
|
|||||||
15
server/middleware/eventAuthorizationMIddleware.js
Normal file
15
server/middleware/eventAuthorizationMIddleware.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* Checks if the event secret is correct
|
||||||
|
* @param req
|
||||||
|
* @param res
|
||||||
|
* @param next
|
||||||
|
*/
|
||||||
|
function eventAuthorizationMiddleware(req, res, next) {
|
||||||
|
if (req.headers["event-secret"] !== process.env.EVENT_SECRET) {
|
||||||
|
return res.status(401).send("Unauthorized");
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = eventAuthorizationMiddleware;
|
||||||
15
server/middleware/validateAdminMiddleware.js
Normal file
15
server/middleware/validateAdminMiddleware.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
const logger = require("../utils/logger");
|
||||||
|
const adminEmail = require("../utils/adminEmail");
|
||||||
|
|
||||||
|
const validateAdminMiddleware = (req, res, next) => {
|
||||||
|
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
|
||||||
|
logger.log("admin-validation-failed", "ERROR", req.user.email, null, {
|
||||||
|
request: req.body,
|
||||||
|
user: req.user,
|
||||||
|
});
|
||||||
|
return res.sendStatus(404);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = validateAdminMiddleware;
|
||||||
59
server/middleware/validateFirebaseIdTokenMiddleware.js
Normal file
59
server/middleware/validateFirebaseIdTokenMiddleware.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
const logger = require("../utils/logger");
|
||||||
|
const admin = require("firebase-admin");
|
||||||
|
|
||||||
|
const validateFirebaseIdTokenMiddleware = async (req, res, next) => {
|
||||||
|
if (
|
||||||
|
(!req.headers.authorization ||
|
||||||
|
!req.headers.authorization.startsWith("Bearer ")) &&
|
||||||
|
!(req.cookies && req.cookies.__session)
|
||||||
|
) {
|
||||||
|
console.error("Unauthorized attempt. No authorization provided.");
|
||||||
|
res.status(403).send("Unauthorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let idToken;
|
||||||
|
|
||||||
|
if (
|
||||||
|
req.headers.authorization &&
|
||||||
|
req.headers.authorization.startsWith("Bearer ")
|
||||||
|
) {
|
||||||
|
// console.log('Found "Authorization" header');
|
||||||
|
// Read the ID Token from the Authorization header.
|
||||||
|
idToken = req.headers.authorization.split("Bearer ")[1];
|
||||||
|
} else if (req.cookies) {
|
||||||
|
//console.log('Found "__session" cookie');
|
||||||
|
// Read the ID Token from cookie.
|
||||||
|
idToken = req.cookies.__session;
|
||||||
|
} else {
|
||||||
|
// No cookie
|
||||||
|
console.error("Unauthorized attempt. No cookie provided.");
|
||||||
|
logger.log("api-unauthorized-call", "WARN", null, null, {
|
||||||
|
req,
|
||||||
|
type: "no-cookie",
|
||||||
|
});
|
||||||
|
res.status(403).send("Unauthorized");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const decodedIdToken = await admin.auth().verifyIdToken(idToken);
|
||||||
|
//console.log("ID Token correctly decoded", decodedIdToken);
|
||||||
|
req.user = decodedIdToken;
|
||||||
|
next();
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.log("api-unauthorized-call", "WARN", null, null, {
|
||||||
|
path: req.path,
|
||||||
|
body: req.body,
|
||||||
|
|
||||||
|
type: "unauthroized",
|
||||||
|
...error,
|
||||||
|
});
|
||||||
|
|
||||||
|
res.status(401).send("Unauthorized");
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = validateFirebaseIdTokenMiddleware;
|
||||||
@@ -15,10 +15,6 @@ const {getClient} = require('../../libs/awsUtils');
|
|||||||
|
|
||||||
|
|
||||||
async function OpenSearchUpdateHandler(req, res) {
|
async function OpenSearchUpdateHandler(req, res) {
|
||||||
if (req.headers["event-secret"] !== process.env.EVENT_SECRET) {
|
|
||||||
res.status(401).send("Unauthorized");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const osClient = await getClient();
|
const osClient = await getClient();
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fb = require('../firebase/firebase-handler');
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
const accountQbxml = require('../accounting/qbxml/qbxml');
|
const {payments, payables, receivables} = require("../accounting/qbxml/qbxml");
|
||||||
|
|
||||||
router.post('/qbxml/receivables', fb.validateFirebaseIdToken, accountQbxml.receivables);
|
router.use(validateFirebaseIdTokenMiddleware);
|
||||||
router.post('/qbxml/payables', fb.validateFirebaseIdToken, accountQbxml.payables);
|
|
||||||
router.post('/qbxml/payments', fb.validateFirebaseIdToken, accountQbxml.payments);
|
router.post('/qbxml/receivables', receivables);
|
||||||
|
router.post('/qbxml/payables', payables);
|
||||||
|
router.post('/qbxml/payments', payments);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fb = require('../firebase/firebase-handler');
|
const fb = require('../firebase/firebase-handler');
|
||||||
const adm = require('../admin/adminops');
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
const {createAssociation, createShop, updateShop, updateCounter} = require("../admin/adminops");
|
||||||
|
const validateAdminMiddleware = require("../middleware/validateAdminMiddleware");
|
||||||
|
|
||||||
|
router.use(validateFirebaseIdTokenMiddleware);
|
||||||
|
|
||||||
router.post('/createassociation', fb.validateFirebaseIdToken, fb.validateAdmin, adm.createAssociation);
|
router.post('/createassociation', validateAdminMiddleware, createAssociation);
|
||||||
router.post('/createshop', fb.validateFirebaseIdToken, fb.validateAdmin, adm.createShop);
|
router.post('/createshop', validateAdminMiddleware, createShop);
|
||||||
router.post('/updateshop', fb.validateFirebaseIdToken, fb.validateAdmin, adm.updateShop);
|
router.post('/updateshop', validateAdminMiddleware, updateShop);
|
||||||
router.post('/updatecounter', fb.validateFirebaseIdToken, fb.validateAdmin, adm.updateCounter);
|
router.post('/updatecounter', validateAdminMiddleware, updateCounter);
|
||||||
router.post('/updateuser', fb.validateFirebaseIdToken, fb.updateUser);
|
router.post('/updateuser', fb.updateUser);
|
||||||
router.post('/getuser', fb.validateFirebaseIdToken, fb.getUser);
|
router.post('/getuser', fb.getUser);
|
||||||
router.post('/createuser', fb.validateFirebaseIdToken, fb.createUser);
|
router.post('/createuser', fb.createUser);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fb = require('../firebase/firebase-handler');
|
|
||||||
const cdkGetMake = require('../cdk/cdk-get-makes');
|
const cdkGetMake = require('../cdk/cdk-get-makes');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
|
||||||
router.post('/getvehicles', fb.validateFirebaseIdToken, cdkGetMake.default);
|
router.use(validateFirebaseIdTokenMiddleware);
|
||||||
|
|
||||||
|
router.post('/getvehicles', cdkGetMake.default);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const data = require('../data/data');
|
const {autohouse, claimscorp, kaizen} = require('../data/data');
|
||||||
|
|
||||||
router.post('/ah', data.autohouse);
|
router.post('/ah', autohouse);
|
||||||
router.post('/cc', data.claimscorp);
|
router.post('/cc', claimscorp);
|
||||||
router.post('/kaizen', data.kaizen);
|
router.post('/kaizen', kaizen);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fb = require('../firebase/firebase-handler');
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
const intellipay = require('../intellipay/intellipay');
|
const {lightbox_credentials, payment_refund, generate_payment_url, postback} = require("../intellipay/intellipay");
|
||||||
|
|
||||||
router.post('/lightbox_credentials', fb.validateFirebaseIdToken, intellipay.lightbox_credentials);
|
router.post('/lightbox_credentials', validateFirebaseIdTokenMiddleware, lightbox_credentials);
|
||||||
router.post('/payment_refund', fb.validateFirebaseIdToken, intellipay.payment_refund);
|
router.post('/payment_refund', validateFirebaseIdTokenMiddleware, payment_refund);
|
||||||
router.post('/generate_payment_url', fb.validateFirebaseIdToken, intellipay.generate_payment_url);
|
router.post('/generate_payment_url', validateFirebaseIdTokenMiddleware, generate_payment_url);
|
||||||
router.post('/postback', intellipay.postback);
|
router.post('/postback', postback);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,15 +1,19 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fb = require('../firebase/firebase-handler');
|
|
||||||
const job = require('../job/job');
|
const job = require('../job/job');
|
||||||
const partsScan = require('../parts-scan/parts-scan');
|
const {partsScan} = require('../parts-scan/parts-scan');
|
||||||
|
const eventAuthorizationMiddleware = require('../middleware/eventAuthorizationMIddleware');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
const {totals, statustransition, totalsSsu, costing, lifecycle, costingmulti} = require("../job/job");
|
||||||
|
|
||||||
router.post('/totals', fb.validateFirebaseIdToken, job.totals);
|
router.use(validateFirebaseIdTokenMiddleware);
|
||||||
router.post('/statustransition', fb.validateFirebaseIdToken, job.statustransition);
|
|
||||||
router.post('/totalsssu', fb.validateFirebaseIdToken, job.totalsSsu);
|
router.post('/totals', totals);
|
||||||
router.post('/costing', fb.validateFirebaseIdToken, job.costing);
|
router.post('/statustransition', eventAuthorizationMiddleware, statustransition);
|
||||||
router.get('/lifecycle', fb.validateFirebaseIdToken, job.lifecycle);
|
router.post('/totalsssu', totalsSsu);
|
||||||
router.post('/costingmulti', fb.validateFirebaseIdToken, job.costingmulti);
|
router.post('/costing', costing);
|
||||||
router.post('/partsscan', fb.validateFirebaseIdToken, partsScan.partsScan);
|
router.get('/lifecycle', lifecycle);
|
||||||
|
router.post('/costingmulti', costingmulti);
|
||||||
|
router.post('/partsscan', partsScan);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fb = require('../firebase/firebase-handler');
|
const {createSignedUploadURL, downloadFiles, renameKeys, deleteFiles} = require('../media/media');
|
||||||
const media = require('../media/media');
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
|
||||||
router.post('/sign', fb.validateFirebaseIdToken, media.createSignedUploadURL);
|
router.use(validateFirebaseIdTokenMiddleware);
|
||||||
router.post('/download', fb.validateFirebaseIdToken, media.downloadFiles);
|
|
||||||
router.post('/rename', fb.validateFirebaseIdToken, media.renameKeys);
|
router.post('/sign', createSignedUploadURL);
|
||||||
router.post('/delete', fb.validateFirebaseIdToken, media.deleteFiles);
|
router.post('/download', downloadFiles);
|
||||||
|
router.post('/rename', renameKeys);
|
||||||
|
router.post('/delete', deleteFiles);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -3,11 +3,12 @@ const router = express.Router();
|
|||||||
const logger = require("../../server/utils/logger");
|
const logger = require("../../server/utils/logger");
|
||||||
const sendEmail = require("../email/sendemail");
|
const sendEmail = require("../email/sendemail");
|
||||||
const data = require("../data/data");
|
const data = require("../data/data");
|
||||||
const fb = require("../firebase/firebase-handler");
|
|
||||||
const bodyParser = require("body-parser");
|
const bodyParser = require("body-parser");
|
||||||
const ioevent = require("../ioevent/ioevent");
|
const ioevent = require("../ioevent/ioevent");
|
||||||
const taskHandler = require("../tasks/tasks");
|
const taskHandler = require("../tasks/tasks");
|
||||||
const os = require("../opensearch/os-handler");
|
const os = require("../opensearch/os-handler");
|
||||||
|
const eventAuthorizationMiddleware = require("../middleware/eventAuthorizationMIddleware");
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
|
||||||
//Test route to ensure Express is responding.
|
//Test route to ensure Express is responding.
|
||||||
router.get("/test", async function (req, res) {
|
router.get("/test", async function (req, res) {
|
||||||
@@ -29,15 +30,21 @@ router.get("/test", async function (req, res) {
|
|||||||
res.status(200).send(`OK - ${commit}`);
|
res.status(200).send(`OK - ${commit}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
router.post("/search", fb.validateFirebaseIdToken, os.search);
|
// Search
|
||||||
router.post("/opensearch", os.handler);
|
router.post("/search", validateFirebaseIdTokenMiddleware, os.search);
|
||||||
|
router.post("/opensearch", eventAuthorizationMiddleware, os.handler);
|
||||||
|
|
||||||
|
|
||||||
|
// IO Events
|
||||||
router.post('/ioevent', ioevent.default);
|
router.post('/ioevent', ioevent.default);
|
||||||
router.post('/sendemail', fb.validateFirebaseIdToken, sendEmail.sendEmail);
|
|
||||||
|
// Email
|
||||||
|
router.post('/sendemail', validateFirebaseIdTokenMiddleware, sendEmail.sendEmail);
|
||||||
router.post('/emailbounce', bodyParser.text(), sendEmail.emailBounce);
|
router.post('/emailbounce', bodyParser.text(), sendEmail.emailBounce);
|
||||||
|
|
||||||
|
// Handlers
|
||||||
router.post('/record-handler/arms', data.arms);
|
router.post('/record-handler/arms', data.arms);
|
||||||
router.post("/taskHandler", fb.validateFirebaseIdToken, taskHandler.taskHandler);
|
router.post("/taskHandler", validateFirebaseIdTokenMiddleware, taskHandler.taskHandler);
|
||||||
|
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ const express = require('express');
|
|||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const multer = require('multer');
|
const multer = require('multer');
|
||||||
const upload = multer();
|
const upload = multer();
|
||||||
const fb = require('../firebase/firebase-handler');
|
const {mixdataUpload} = require('../mixdata/mixdata');
|
||||||
const mixdataUpload = require('../mixdata/mixdata');
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
|
||||||
router.post('/upload', fb.validateFirebaseIdToken, upload.any(), mixdataUpload.mixdataUpload);
|
router.post('/upload', validateFirebaseIdTokenMiddleware, upload.any(), mixdataUpload);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
const {subscribe, unsubscribe} = require("../firebase/firebase-handler");
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fb = require('../firebase/firebase-handler');
|
|
||||||
|
|
||||||
router.post('/subscribe', fb.validateFirebaseIdToken, fb.subscribe);
|
router.use(validateFirebaseIdTokenMiddleware);
|
||||||
router.post('/unsubscribe', fb.validateFirebaseIdToken, fb.unsubscribe);
|
|
||||||
|
router.post('/subscribe', subscribe);
|
||||||
|
router.post('/unsubscribe', unsubscribe);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fb = require('../firebase/firebase-handler');
|
const {authorize, callback, receivables, payables, payments} = require('../accounting/qbo/qbo');
|
||||||
const qbo = require('../accounting/qbo/qbo'); // Assuming you have a qbo module for handling QuickBooks Online related functionalities
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); // Assuming you have a qbo module for handling QuickBooks Online related functionalities
|
||||||
|
|
||||||
// Define the routes for QuickBooks Online
|
// Define the routes for QuickBooks Online
|
||||||
router.post('/authorize', fb.validateFirebaseIdToken, qbo.authorize);
|
router.post('/authorize', validateFirebaseIdTokenMiddleware, authorize);
|
||||||
router.get('/callback', qbo.callback);
|
router.get('/callback', callback);
|
||||||
router.post('/receivables', fb.validateFirebaseIdToken, qbo.receivables);
|
router.post('/receivables', validateFirebaseIdTokenMiddleware, receivables);
|
||||||
router.post('/payables', fb.validateFirebaseIdToken, qbo.payables);
|
router.post('/payables', validateFirebaseIdTokenMiddleware, payables);
|
||||||
router.post('/payments', fb.validateFirebaseIdToken, qbo.payments);
|
router.post('/payments', validateFirebaseIdTokenMiddleware, payments);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fb = require('../firebase/firebase-handler');
|
const {inlinecss} = require('../render/inlinecss');
|
||||||
const inlineCss = require('../render/inlinecss');
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
|
||||||
// Define the route for inline CSS rendering
|
// Define the route for inline CSS rendering
|
||||||
router.post('/inlinecss', fb.validateFirebaseIdToken, inlineCss.inlinecss);
|
router.post('/inlinecss', validateFirebaseIdTokenMiddleware, inlinecss);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fb = require('../firebase/firebase-handler');
|
const {job} = require('../scheduling/scheduling-job');
|
||||||
const scheduling = require('../scheduling/scheduling-job');
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
|
||||||
router.post('/job', fb.validateFirebaseIdToken, scheduling.job);
|
router.post('/job', validateFirebaseIdTokenMiddleware, job);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const twilio = require('twilio');
|
const twilio = require('twilio');
|
||||||
const fb = require('../firebase/firebase-handler');
|
const {receive} = require('../sms/receive');
|
||||||
const smsReceive = require('../sms/receive');
|
const {send} = require('../sms/send');
|
||||||
const smsSend = require('../sms/send');
|
const {status, markConversationRead} = require('../sms/status');
|
||||||
const smsStatus = require('../sms/status');
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
|
||||||
// Twilio Webhook Middleware for production
|
// Twilio Webhook Middleware for production
|
||||||
const twilioWebhookMiddleware = twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" });
|
const twilioWebhookMiddleware = twilio.webhook({ validate: process.env.NODE_ENV === "PRODUCTION" });
|
||||||
|
|
||||||
router.post('/receive', twilioWebhookMiddleware, smsReceive.receive);
|
router.post('/receive', twilioWebhookMiddleware, receive);
|
||||||
router.post('/send', fb.validateFirebaseIdToken, smsSend.send);
|
router.post('/send', validateFirebaseIdTokenMiddleware, send);
|
||||||
router.post('/status', twilioWebhookMiddleware, smsStatus.status);
|
router.post('/status', twilioWebhookMiddleware, status);
|
||||||
router.post('/markConversationRead', fb.validateFirebaseIdToken, smsStatus.markConversationRead);
|
router.post('/markConversationRead', validateFirebaseIdTokenMiddleware, markConversationRead);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fb = require('../firebase/firebase-handler');
|
const {techLogin} = require('../tech/tech');
|
||||||
const tech = require('../tech/tech');
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
|
||||||
router.post('/login', fb.validateFirebaseIdToken, tech.techLogin);
|
router.post('/login', validateFirebaseIdTokenMiddleware, techLogin);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const fb = require('../firebase/firebase-handler');
|
const {servertime, jsrAuth} = require('../utils/utils');
|
||||||
const utils = require('../utils/utils');
|
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
|
||||||
|
|
||||||
router.post('/time', utils.servertime);
|
router.post('/time', servertime);
|
||||||
router.post('/jsr', fb.validateFirebaseIdToken, utils.jsrAuth);
|
router.post('/jsr', validateFirebaseIdTokenMiddleware, jsrAuth);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
13
server/utils/adminEmail.js
Normal file
13
server/utils/adminEmail.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/**
|
||||||
|
* List of admin email addresses
|
||||||
|
* @type {string[]}
|
||||||
|
*/
|
||||||
|
const adminEmail = [
|
||||||
|
"patrick@imex.dev",
|
||||||
|
//"patrick@imex.test",
|
||||||
|
"patrick@imex.prod",
|
||||||
|
"patrick@imexsystems.ca",
|
||||||
|
"patrick@thinkimex.com",
|
||||||
|
];
|
||||||
|
|
||||||
|
module.exports = adminEmail;
|
||||||
Reference in New Issue
Block a user