Merge pull request #2460 from nxglabs/sync-to-public_repo-25861367352

Merge pull request #2459 from nxglabs/staging
This commit is contained in:
raktima-opensignlabs
2026-05-15 15:44:29 +00:00
parent 197c00dd79
commit 56a119a901
92 changed files with 8850 additions and 5394 deletions
@@ -1,7 +1,7 @@
async function DocumentAftersave(request) {
try {
if (!request.original) {
console.log('new entry is insert in contracts_Document');
console.log('new entry is insert in contracts_Document ', request?.object?.id);
const obj = request.object;
const objId = obj?.id;
const createdAt = obj?.get?.('createdAt');
@@ -26,6 +26,8 @@ export default async function GetTemplate(request) {
template.include('CreatedBy');
template.include('ExtUserPtr.TenantId');
template.include('Bcc');
template.include('Cc');
const extUserQuery = new Parse.Query('contracts_Users');
extUserQuery.equalTo('Email', userEmail);
extUserQuery.include('TeamIds');
@@ -62,6 +64,7 @@ export default async function GetTemplate(request) {
template.include('ExtUserPtr.TenantId');
template.include('Placeholders.signerPtr');
template.include('Bcc');
template.include('Cc');
}
}
const res = await template.first({ useMasterKey: true });
@@ -1,7 +1,7 @@
export default async function TemplateAfterSave(request) {
try {
if (!request.original) {
console.log('new entry is insert in contracts_Template');
console.log('new entry is insert in contracts_Template', request?.object?.id);
const obj = request.object;
const objId = obj?.id;
const ip = request?.headers?.['x-real-ip'] || '';
@@ -3,6 +3,7 @@ import { cloudServerUrl, mailTemplate, replaceMailVaribles, serverAppId } from '
import { setDocumentCount } from '../../utils/CountUtils.js';
import crypto from 'crypto';
import sendSystemMail from './sendSystemMail.js';
function chunkArray(arr, size) {
const out = [];
@@ -50,9 +51,6 @@ async function sendOwnerSummaryEmail({
failedList,
}) {
try {
const url = `${serverUrl}/functions/sendmailv3`;
const headers = { 'Content-Type': 'application/json', 'X-Parse-Application-Id': appId };
const subject = `Bulk send finished: ${failed} of ${total} failed to create`;
const failureHtml = failedList?.length
@@ -83,7 +81,7 @@ async function sendOwnerSummaryEmail({
html,
};
await axios.post(url, params, { headers });
await sendSystemMail({ params });
} catch (e) {
console.log('batchdoc Failed to send owner summary email:', e?.message || e);
}
@@ -92,7 +90,7 @@ async function sendOwnerSummaryEmail({
async function deductcount(docsCount, extUserId) {
try {
if (extUserId) {
setDocumentCount(extUserId);
setDocumentCount(extUserId, docsCount);
}
} catch (err) {
console.log('batchdoc deductcount error: ', err);
@@ -230,6 +228,7 @@ async function startBulkSendInBackground(userId, Documents, Ip, parseConfig, typ
Description: x.Description,
CreatedBy: x.CreatedBy,
SendinOrder: x.SendinOrder || true,
SendInOrderStrict: x.SendInOrderStrict || false,
ExtUserPtr: {
__type: 'Pointer',
className: x.ExtUserPtr.className,
@@ -272,6 +271,7 @@ async function startBulkSendInBackground(userId, Documents, Ip, parseConfig, typ
...(x?.SignatureType ? { SignatureType: x?.SignatureType } : {}),
...(x?.NotifyOnSignatures ? { NotifyOnSignatures: x?.NotifyOnSignatures } : {}),
...(x?.Bcc?.length > 0 ? { Bcc: x?.Bcc } : {}),
...(x?.Cc?.length > 0 ? { Cc: x?.Cc } : {}),
...(x?.RedirectUrl ? { RedirectUrl: x?.RedirectUrl } : {}),
...(mailBody ? { RequestBody: mailBody } : {}),
...(mailSubject ? { RequestSubject: mailSubject } : {}),
@@ -303,7 +303,6 @@ async function startBulkSendInBackground(userId, Documents, Ip, parseConfig, typ
createdAt: response.data[0]?.success?.createdAt,
};
deductcount(response.data.length, resExt.id);
console.log('here');
sendMail(updateDocuments, publicUrl); //sessionToken
return { total: 1, created: 1, failed: 0 };
}
@@ -0,0 +1,99 @@
import { setDocumentCount } from '../../utils/CountUtils.js';
export default async function createDocumentFromApp(request) {
const doc = request.params?.document;
if (!doc) {
throw new Parse.Error(Parse.Error.INVALID_JSON, 'Missing document payload.');
}
if (!request.user) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
const SentToOthers = doc.SentToOthers !== undefined ? doc.SentToOthers : false;
const SendinOrder = doc.SendinOrder !== undefined ? doc.SendinOrder : false;
const SendInOrderStrict = doc.SendInOrderStrict !== undefined ? !!doc.SendInOrderStrict : false;
const IsEnableOTP = doc?.IsEnableOTP !== undefined ? doc?.IsEnableOTP : false;
const IsTourEnabled = doc?.IsTourEnabled !== undefined ? doc?.IsTourEnabled : false;
const AllowModifications =
doc?.AllowModifications !== undefined ? doc?.AllowModifications : false;
const AutomaticReminders =
doc?.AutomaticReminders !== undefined ? doc?.AutomaticReminders : false;
const NotifyOnSignatures = doc.NotifyOnSignatures !== undefined ? doc.NotifyOnSignatures : false;
try {
const docCls = new Parse.Object('contracts_Document');
docCls.set('Name', doc?.Name || 'untitled document');
docCls.set('URL', doc?.URL);
docCls.set('ExtUserPtr', doc.ExtUserPtr);
docCls.set('CreatedBy', doc.CreatedBy);
if (doc.Description) {
docCls.set('Description', doc.Description);
}
if (doc.Note) {
docCls.set('Note', doc.Note);
}
if (doc.SignedUrl) {
docCls.set('SignedUrl', doc.SignedUrl);
}
docCls.set('SentToOthers', SentToOthers);
docCls.set('SendinOrder', SendinOrder);
docCls.set('SendInOrderStrict', SendInOrderStrict);
docCls.set('IsEnableOTP', IsEnableOTP);
docCls.set('IsTourEnabled', IsTourEnabled);
docCls.set('AllowModifications', AllowModifications);
docCls.set('AutomaticReminders', AutomaticReminders);
docCls.set('NotifyOnSignatures', NotifyOnSignatures);
if (doc.TimeToCompleteDays !== undefined) {
docCls.set('TimeToCompleteDays', Number(doc.TimeToCompleteDays));
}
if (doc.RemindOnceInEvery !== undefined) {
docCls.set('RemindOnceInEvery', Number(doc.RemindOnceInEvery));
}
if (doc?.DocSentAt?.iso) {
docCls.set('DocSentAt', new Date(doc?.DocSentAt?.iso));
}
if (doc.RedirectUrl) {
docCls.set('RedirectUrl', doc.RedirectUrl);
}
if (doc.TemplateId) {
docCls.set('TemplateId', doc.TemplateId);
}
if (Array.isArray(doc.Signers) && doc?.Signers?.length > 0) {
docCls.set('Signers', doc.Signers);
}
if (Array.isArray(doc.Placeholders) && doc?.Placeholders?.length > 0) {
docCls.set('Placeholders', doc.Placeholders);
}
if (Array.isArray(doc.SignatureType) && doc.SignatureType.length > 0) {
docCls.set('SignatureType', doc.SignatureType);
}
if (Array.isArray(doc.Bcc) && doc?.Bcc?.length > 0) {
docCls.set('Bcc', doc.Bcc);
}
if (Array.isArray(doc.Cc) && doc?.Cc?.length > 0) {
docCls.set('Cc', doc.Cc);
}
if (Array.isArray(doc?.PenColors) && doc?.PenColors?.length > 0) {
docCls.set('PenColors', doc.PenColors);
}
const docRes = await docCls.save(null, { useMasterKey: true });
// update documentCount in Users and tenant account
setDocumentCount(doc?.ExtUserPtr?.id);
return docRes;
} catch (error) {
console.log('error in create document from app: ', error);
throw error;
}
}
@@ -38,6 +38,7 @@ export default async function createDuplicate(request) {
newTemplate.set('SignedUrl', _templateRes.SignedUrl);
newTemplate.set('SentToOthers', _templateRes?.SentToOthers || false);
newTemplate.set('SendinOrder', _templateRes?.SendinOrder || false);
newTemplate.set('SendInOrderStrict', _templateRes?.SendInOrderStrict || false);
newTemplate.set('AutomaticReminders', _templateRes?.AutomaticReminders || false);
newTemplate.set('RemindOnceInEvery', _templateRes?.RemindOnceInEvery || 5);
newTemplate.set('IsEnableOTP', _templateRes?.IsEnableOTP || false);
@@ -77,6 +78,9 @@ export default async function createDuplicate(request) {
if (_templateRes?.Bcc?.length) {
newTemplate.set('Bcc', _templateRes?.Bcc);
}
if (_templateRes?.Cc?.length) {
newTemplate.set('Cc', _templateRes?.Cc);
}
const OriginIp = _templateRes?.OriginIp || request?.headers?.['x-real-ip'] || '';
if (OriginIp) {
@@ -1,13 +1,5 @@
import axios from 'axios';
import { appName, cloudServerUrl, serverAppId } from '../../Utils.js';
const serverUrl = cloudServerUrl;
const APPID = serverAppId;
const masterKEY = process.env.MASTER_KEY;
const headers = {
'Content-Type': 'application/json',
'X-Parse-Application-Id': APPID,
'X-Parse-Master-Key': masterKEY,
};
import { appName } from '../../Utils.js';
import sendSystemMail from './sendSystemMail.js';
async function sendDeclineMail(doc, publicUrl, userId, reason) {
try {
@@ -46,7 +38,7 @@ async function sendDeclineMail(doc, publicUrl, userId, reason) {
pdfName: pdfName,
html: body,
};
await axios.post(serverUrl + '/functions/sendmailv3', params, { headers });
await sendSystemMail({ params });
} catch (err) {
console.log('err in sendnotifymail', err);
}
@@ -63,16 +55,21 @@ export default async function declinedocument(request) {
try {
const docCls = new Parse.Query('contracts_Document');
docCls.include('ExtUserPtr.TenantId,Placeholders.signerPtr,Signers');
docCls.notEqualTo('IsCompleted', true);
docCls.notEqualTo('IsArchive', true);
const updateDoc = await docCls.get(docId, { useMasterKey: true });
if (updateDoc) {
const _doc = JSON.parse(JSON.stringify(updateDoc));
const isEnableOTP = updateDoc?.get('IsEnableOTP') || false;
const isCreator = _doc?.CreatedBy?.objectId === userId;
if (!isEnableOTP) {
updateDoc.set('IsDeclined', true);
updateDoc.set('DeclineReason', reason);
updateDoc.set('DeclineBy', declineBy);
await updateDoc.save(null, { useMasterKey: true });
sendDeclineMail(_doc, publicUrl, userId, reason);
if (!isCreator) {
sendDeclineMail(_doc, publicUrl, userId, reason);
}
return 'document declined';
} else {
if (!request?.user) {
@@ -82,7 +79,10 @@ export default async function declinedocument(request) {
updateDoc.set('DeclineReason', reason);
updateDoc.set('DeclineBy', declineBy);
await updateDoc.save(null, { useMasterKey: true });
sendDeclineMail(_doc, publicUrl, userId, reason);
const isCreator = _doc?.CreatedBy?.objectId === request?.user?.id;
if (!isCreator) {
sendDeclineMail(_doc, publicUrl, userId, reason);
}
return 'document declined';
}
} else {
@@ -17,11 +17,6 @@ async function uploadFile(pdfName, filepath) {
const filedata = fs.readFileSync(filepath);
let fileUrl;
// const file = new Parse.File(pdfName, [...filedata], 'application/pdf');
// await file.save({ useMasterKey: true });
// const fileRes = getSecureUrl(file.url());
// fileUrl = fileRes.url;
const fileRes = await parseUploadFile(pdfName, filedata, 'application/pdf');
fileUrl = getSecureUrl(fileRes?.url)?.url;
return { imageUrl: fileUrl };
@@ -43,7 +43,7 @@ function makeS3Client() {
}
export default async function getPresignedUrl(url) {
if (url?.includes('files')) {
if (url?.includes('/files/')) {
return presignedlocalUrl(url);
} else {
const client = makeS3Client();
@@ -70,7 +70,7 @@ export async function getSignedUrl(request) {
if (docId || templateId) {
try {
if (url?.includes('files')) {
if (url?.includes('/files/')) {
return presignedlocalUrl(url);
} else if (useLocal !== 'true') {
const query = new Parse.Query(docId ? 'contracts_Document' : 'contracts_Template');
@@ -106,7 +106,7 @@ export async function getSignedUrl(request) {
if (!isAuth) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
} else {
if (url?.includes('files')) {
if (url?.includes('/files/')) {
return presignedlocalUrl(url);
} else if (useLocal !== 'true') {
const presignedUrl = await getPresignedUrl(url);
@@ -147,7 +147,7 @@ export function getSignedLocalUrl(fileUrl, expirationTimeInSeconds) {
}
export function presignedlocalUrl(signedUrl, expirationTimeInSeconds) {
if (signedUrl?.includes('files')) {
if (signedUrl?.includes('/files/')) {
const fileUrl = signedUrl.split('?')?.[0];
const secretKey = process.env.MASTER_KEY;
const exp = expirationTimeInSeconds || 200;
@@ -3,6 +3,13 @@ import fs from 'node:fs';
import fontkit from '@pdf-lib/fontkit';
import { formatDateTime } from '../../../Utils.js';
const formatDateStr = (dateStr, DateFormat, timezone, Is12Hr) => {
if (!dateStr) return '';
const date = new Date(dateStr);
if (isNaN(date.getTime())) return dateStr;
return formatDateTime(date, DateFormat, timezone, Is12Hr);
};
export default async function GenerateCertificate(docDetails) {
const timezone = docDetails?.ExtUserPtr?.Timezone || '';
const Is12Hr = docDetails?.ExtUserPtr?.Is12HourTime || false;
@@ -12,7 +19,9 @@ export default async function GenerateCertificate(docDetails) {
const fontBytes = fs.readFileSync('./font/times.ttf'); //
pdfDoc.registerFontkit(fontkit);
const timesRomanFont = await pdfDoc.embedFont(fontBytes, { subset: true });
const pngUrl = fs.readFileSync('./logo.png').buffer;
const pngUrl = fs.readFileSync('./images/logo.png').buffer;
const naSignUrl = fs.readFileSync('./images/na_sign.png').buffer;
const nasign = await pdfDoc.embedPng(naSignUrl);
const pngImage = await pdfDoc.embedPng(pngUrl);
const page = pdfDoc.addPage();
const { width, height } = page.getSize();
@@ -29,7 +38,7 @@ export default async function GenerateCertificate(docDetails) {
const textKeyColor = rgb(0.12, 0.12, 0.12);
const textValueColor = rgb(0.3, 0.3, 0.3);
const completedAt = docDetails?.completedAt ? new Date(docDetails?.completedAt) : new Date();
const completedAtperTimezone = formatDateTime(completedAt, DateFormat, timezone, Is12Hr);
const completedAtperTimezone = formatDateStr(completedAt, DateFormat, timezone, Is12Hr);
const completedUTCtime = completedAtperTimezone;
const signersCount = docDetails?.Signers?.length || 1;
const generateAt = docDetails?.completedAt ? new Date(docDetails?.completedAt) : new Date();
@@ -43,9 +52,19 @@ export default async function GenerateCertificate(docDetails) {
const company = docDetails?.ExtUserPtr?.Company || '';
const documentHash = docDetails?.DocumentHash || '';
const createdAt = docDetails?.DocSentAt?.iso || docDetails.createdAt;
const createdAtperTimezone = formatDateTime(createdAt, DateFormat, timezone, Is12Hr);
const createdAtperTimezone = formatDateStr(createdAt, DateFormat, timezone, Is12Hr);
const IsEnableOTP = docDetails?.IsEnableOTP || false;
const filteredaudit = docDetails?.AuditTrail?.filter(x => x?.UserPtr?.objectId);
const placeholders = Array.isArray(docDetails?.Placeholders) ? docDetails.Placeholders : [];
const filteredaudit = docDetails?.AuditTrail?.filter(x => {
if (!x?.UserPtr?.objectId) return false;
return true;
});
const toTs = v => {
if (!v) return 0;
if (typeof v === 'object' && v?.iso) return new Date(v.iso).getTime() || 0;
const t = new Date(v).getTime();
return Number.isFinite(t) ? t : 0;
};
const auditTrail =
docDetails?.Signers?.length > 0
? filteredaudit?.map(x => {
@@ -56,6 +75,7 @@ export default async function GenerateCertificate(docDetails) {
SignedOn: x?.SignedOn || generatedUTCTime,
ViewedOn: x?.ViewedOn || x?.SignedOn || generatedUTCTime,
Signature: x?.Signature || '',
_signedOnTs: toTs(x?.SignedOn),
};
})
: [
@@ -65,8 +85,12 @@ export default async function GenerateCertificate(docDetails) {
SignedOn: filteredaudit[0]?.SignedOn || generatedUTCTime,
ViewedOn: filteredaudit[0]?.ViewedOn || filteredaudit[0]?.SignedOn || generatedUTCTime,
Signature: filteredaudit[0]?.Signature || '',
_signedOnTs: toTs(filteredaudit[0]?.SignedOn),
},
];
if (Array.isArray(auditTrail)) {
auditTrail.sort((a, b) => (a?._signedOnTs || 0) - (b?._signedOnTs || 0));
}
const ownerName = docDetails?.SenderName || docDetails.ExtUserPtr?.Name || 'n/a';
const ownerEmail = docDetails?.SenderMail || docDetails.ExtUserPtr?.Email || 'n/a';
@@ -303,24 +327,63 @@ export default async function GenerateCertificate(docDetails) {
let yPosition7 = yPosition6 - 20;
let yPosition8 = yPosition7 - 35;
auditTrail.slice(0, 3).forEach(async (x, i) => {
const embedPng = x.Signature ? await pdfDoc.embedPng(x.Signature) : '';
page.drawText(`Signer ${i + 1}`, {
// A signer/approver block spans from yPosition1 down to yPosition8 (the
// separator line at the bottom of the block). The signature image's
// bottom edge sits at yPosition7 - 30 and must remain inside the page
// border (whose bottom edge is at startY). Use the lowest of those two
// values when deciding whether the next block fits on the current page.
const minY = startY + 5;
const blockBottom = () => Math.min(yPosition7 - 30, yPosition8);
// Helper that resets the y-positions to the top of a freshly added page so
// the next block starts cleanly under the border.
const startNewPage = () => {
const newPage = pdfDoc.addPage();
newPage.drawRectangle({
x: startX,
y: startY,
width: width - 2 * startX,
height: height - 2 * startY,
borderColor: borderColor,
borderWidth: 1,
});
yPosition1 = newPage.getHeight() - 40;
yPosition2 = yPosition1 - 20;
yPosition3 = yPosition2 - 20;
yPosition4 = yPosition3 - 20;
yPosition5 = yPosition4 - 20;
yPosition6 = yPosition5 - 20;
yPosition7 = yPosition6 - 20;
yPosition8 = yPosition7 - 35;
return newPage;
};
let currentPage = page;
for (let i = 0; i < auditTrail.length; i++) {
const x = auditTrail[i];
// If the next block would overflow the bottom border, move to a new page.
if (blockBottom() < minY) {
currentPage = startNewPage();
}
const embedPng = x.Signature ? await pdfDoc.embedPng(x.Signature) : nasign;
const headerLabel = `${i + 1}. ${'Signer'}`;
const signedOnLabel = 'Signed on :';
currentPage.drawText(headerLabel, {
x: 30,
y: yPosition1,
size: subtitle,
font: timesRomanFont,
color: titleColor,
});
page.drawText('Name :', {
currentPage.drawText('Name :', {
x: 30,
y: yPosition2,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText(x?.Name, {
currentPage.drawText(x?.Name || '', {
x: 75,
y: yPosition2,
size: signertext,
@@ -329,14 +392,14 @@ export default async function GenerateCertificate(docDetails) {
});
if (IsEnableOTP) {
page.drawText('Security level :', {
currentPage.drawText('Security level :', {
x: half + 120,
y: yPosition2,
size: timeText,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText('Email, OTP Auth', {
currentPage.drawText('Email, OTP Auth', {
x: half + 190,
y: yPosition2,
size: timeText,
@@ -345,15 +408,14 @@ export default async function GenerateCertificate(docDetails) {
});
}
page.drawText('Email :', {
currentPage.drawText('Email :', {
x: 30,
y: yPosition3,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText(x?.Email, {
currentPage.drawText(x?.Email || '', {
x: 75,
y: yPosition3,
size: signertext,
@@ -361,15 +423,14 @@ export default async function GenerateCertificate(docDetails) {
color: textValueColor,
});
page.drawText('Viewed on :', {
currentPage.drawText('Viewed on :', {
x: 30,
y: yPosition4,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText(`${formatDateTime(x.ViewedOn, DateFormat, timezone, Is12Hr)}`, {
currentPage.drawText(`${formatDateStr(x?.ViewedOn, DateFormat, timezone, Is12Hr)}`, {
x: 97,
y: yPosition4,
size: signertext,
@@ -377,31 +438,30 @@ export default async function GenerateCertificate(docDetails) {
color: textValueColor,
});
page.drawText('Signed on :', {
currentPage.drawText(signedOnLabel, {
x: 30,
y: yPosition5,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText(`${formatDateTime(x.SignedOn, DateFormat, timezone, Is12Hr)}`, {
x: 95,
const signedOnValueX = 30 + timesRomanFont.widthOfTextAtSize(signedOnLabel, signertext) + 5;
currentPage.drawText(`${formatDateStr(x?.SignedOn, DateFormat, timezone, Is12Hr)}`, {
x: signedOnValueX,
y: yPosition5,
size: signertext,
font: timesRomanFont,
color: textValueColor,
});
page.drawText('IP address :', {
currentPage.drawText('IP address :', {
x: 30,
y: yPosition6,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
page.drawText(x?.ipAddress, {
currentPage.drawText(x?.ipAddress || '', {
x: 95,
y: yPosition6,
size: signertext,
@@ -409,15 +469,14 @@ export default async function GenerateCertificate(docDetails) {
color: textValueColor,
});
page.drawText('Signature :', {
currentPage.drawText('Signature :', {
x: 30,
y: yPosition7,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
page.drawRectangle({
currentPage.drawRectangle({
x: 98,
y: yPosition7 - 30,
width: 104,
@@ -426,14 +485,14 @@ export default async function GenerateCertificate(docDetails) {
borderWidth: 1,
});
if (embedPng) {
page.drawImage(embedPng, {
currentPage.drawImage(embedPng, {
x: 100,
y: yPosition7 - 27,
width: 100,
height: 40,
});
}
page.drawLine({
currentPage.drawLine({
start: { x: 30, y: yPosition8 },
end: { x: width - 30, y: yPosition8 },
color: rgb(0.12, 0.12, 0.12),
@@ -448,185 +507,6 @@ export default async function GenerateCertificate(docDetails) {
yPosition6 = yPosition5 - 20;
yPosition7 = yPosition6 - 20;
yPosition8 = yPosition8 - 174;
});
if (auditTrail.length > 3) {
let currentPageIndex = 1;
let currentPage = page;
auditTrail.slice(3).forEach(async (x, i) => {
const embedPng = x.Signature ? await pdfDoc.embedPng(x.Signature) : '';
// Calculate remaining space on current page
const remainingSpace = yPosition8;
// If there's not enough space for the next entry, create a new page
if (remainingSpace < 90) {
// Adjust the value as needed
currentPageIndex++;
currentPage = pdfDoc.addPage();
currentPage.drawRectangle({
x: startX,
y: startY,
width: width - 2 * startX,
height: height - 2 * startY,
borderColor: borderColor,
borderWidth: 1,
});
yPosition1 = currentPage.getHeight() - 40;
yPosition2 = yPosition1 - 20;
yPosition3 = yPosition2 - 20;
yPosition4 = yPosition3 - 20;
yPosition5 = yPosition4 - 20;
yPosition5 = yPosition4 - 20;
yPosition6 = yPosition5 - 20;
yPosition7 = yPosition6 - 20;
yPosition8 = currentPage.getHeight() - 190;
}
currentPage.drawText(`Signer ${4 + i}`, {
x: 30,
y: yPosition1,
size: subtitle,
font: timesRomanFont,
color: titleColor,
});
currentPage.drawText('Name :', {
x: 30,
y: yPosition2,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
currentPage.drawText(x?.Name, {
x: 75,
y: yPosition2,
size: signertext,
font: timesRomanFont,
color: textValueColor,
});
if (IsEnableOTP) {
currentPage.drawText('Security level :', {
x: half + 120,
y: yPosition2,
size: timeText,
font: timesRomanFont,
color: textKeyColor,
});
currentPage.drawText(`Email, OTP Auth`, {
x: half + 190,
y: yPosition2,
size: timeText,
font: timesRomanFont,
color: textValueColor,
});
}
currentPage.drawText('Email :', {
x: 30,
y: yPosition3,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
currentPage.drawText(x?.Email, {
x: 75,
y: yPosition3,
size: signertext,
font: timesRomanFont,
color: textValueColor,
});
currentPage.drawText('Viewed on :', {
x: 30,
y: yPosition4,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
currentPage.drawText(`${formatDateTime(x.ViewedOn, DateFormat, timezone, Is12Hr)}`, {
x: 97,
y: yPosition4,
size: signertext,
font: timesRomanFont,
color: textValueColor,
});
currentPage.drawText('Signed on :', {
x: 30,
y: yPosition5,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
currentPage.drawText(`${formatDateTime(x.SignedOn, DateFormat, timezone, Is12Hr)}`, {
x: 95,
y: yPosition5,
size: signertext,
font: timesRomanFont,
color: textValueColor,
});
currentPage.drawText('IP address :', {
x: 30,
y: yPosition6,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
currentPage.drawText(x?.ipAddress, {
x: 100,
y: yPosition6,
size: signertext,
font: timesRomanFont,
color: textValueColor,
});
currentPage.drawText('Signature :', {
x: 30,
y: yPosition7,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
currentPage.drawRectangle({
x: 98,
y: yPosition7 - 27,
width: 104,
height: 44,
borderColor: rgb(0.22, 0.18, 0.47),
borderWidth: 1,
});
if (embedPng) {
currentPage.drawImage(embedPng, {
x: 100,
y: yPosition7 - 25,
width: 100,
height: 40,
});
}
currentPage.drawLine({
start: { x: 30, y: yPosition8 },
end: { x: width - 30, y: yPosition8 },
color: rgb(0.12, 0.12, 0.12),
thickness: 0.5,
});
// Update y positions for the next entry
yPosition1 = yPosition8 - 20;
yPosition2 = yPosition1 - 20;
yPosition3 = yPosition2 - 20;
yPosition4 = yPosition3 - 20;
yPosition5 = yPosition4 - 20;
yPosition6 = yPosition5 - 20;
yPosition7 = yPosition6 - 20;
yPosition8 = yPosition8 - 174;
});
}
const pdfBytes = await pdfDoc.save();
@@ -17,6 +17,22 @@ import { SignPdf } from '@signpdf/signpdf';
import { P12Signer } from '@signpdf/signer-p12';
import { buildDownloadFilename, parseUploadFile } from '../../../utils/fileUtils.js';
import sendMailWithAttachment from '../sendMailWithAttachment.js';
import sendSystemMail from '../sendSystemMail.js';
import {
COMPLETION_ACTIVITIES,
findPlaceholderIndex,
findPendingPriorSigner,
isCompletionRelevant,
} from '../../../utils/workflowUtils.js';
// Audit-trail activities that count toward document completion. The free
// build only counts 'Signed'; EE additionally counts 'Approved'.
// A placeholder participates in completion unless it is a prefill entry.
// EE additionally excludes viewers (who never act on the document).
// Strict-order gating: returns the signerObjId of the prior placeholder
// still pending, or null when the strict-order requirement is satisfied.
const serverUrl = cloudServerUrl; // process.env.SERVER_URL;
const APPID = serverAppId;
@@ -62,13 +78,24 @@ async function uploadFile(pdfName, filepath) {
}
// `updateDoc` is used to update signedUrl, AuditTrail, Iscompleted in document
async function updateDoc(docId, url, userId, ipAddress, data, className, sign, documentHash) {
async function updateDoc(
docId,
url,
userId,
ipAddress,
data,
className,
sign,
documentHash,
activity
) {
try {
const UserPtr = { __type: 'Pointer', className: className, objectId: userId };
const auditActivity = 'Signed';
const obj = {
UserPtr: UserPtr,
SignedUrl: url,
Activity: 'Signed',
Activity: auditActivity,
ipAddress: ipAddress,
SignedOn: new Date(),
Signature: sign,
@@ -88,13 +115,14 @@ async function updateDoc(docId, url, userId, ipAddress, data, className, sign, d
updateAuditTrail = [obj];
}
const auditTrail = updateAuditTrail.filter(x => x.Activity === 'Signed');
// Count both Signed and Approved entries; only signer/approver
// placeholders count toward completion (viewers and prefill excluded).
const auditTrail = updateAuditTrail.filter(x => COMPLETION_ACTIVITIES.includes(x.Activity));
let isCompleted = false;
if (data.Signers && data.Signers.length > 0) {
//'removePrefill' is used to remove prefill role from placeholders filed then compare length to change status of document
const removePrefill =
data.Placeholders.length > 0 && data.Placeholders.filter(x => x.Role !== 'prefill');
if (auditTrail.length === removePrefill?.length) {
const completionRelevant =
data.Placeholders?.length > 0 ? data.Placeholders.filter(isCompletionRelevant) : [];
if (auditTrail.length >= completionRelevant.length && completionRelevant.length > 0) {
isCompleted = true;
}
} else {
@@ -124,10 +152,11 @@ async function sendNotifyMail(doc, signUser, mailProvider, publicUrl) {
const logo =
"<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>";
const auditTrailCount = doc?.AuditTrail?.filter(x => x.Activity === 'Signed')?.length || 0;
const removePrefill =
doc?.Placeholders?.length > 0 && doc?.Placeholders?.filter(x => x?.Role !== 'prefill');
const signersCount = removePrefill?.length;
const auditTrailCount =
doc?.AuditTrail?.filter(x => COMPLETION_ACTIVITIES.includes(x.Activity))?.length || 0;
const completionRelevant =
doc?.Placeholders?.length > 0 ? doc.Placeholders.filter(isCompletionRelevant) : [];
const signersCount = completionRelevant?.length;
const remainingSign = signersCount - auditTrailCount;
if (remainingSign > 1 && doc?.NotifyOnSignatures) {
const sender = doc.ExtUserPtr;
@@ -154,7 +183,7 @@ async function sendNotifyMail(doc, signUser, mailProvider, publicUrl) {
html: body,
mailProvider: mailProvider,
};
await axios.post(serverUrl + '/functions/sendmailv3', params, { headers });
await sendSystemMail({ params });
}
} catch (err) {
console.log('err in sendnotifymail', err);
@@ -239,6 +268,7 @@ async function sendCompletedMail(obj) {
body = replaceVar.body;
}
const Bcc = doc?.Bcc?.length > 0 ? doc.Bcc.map(x => x.Email) : [];
const Cc = doc?.Cc?.length > 0 ? doc.Cc.map(x => x.Email) : [];
const updatedBcc = doc?.SenderMail ? [...Bcc, doc?.SenderMail] : Bcc;
const formatId = doc?.ExtUserPtr?.DownloadFilenameFormat;
const filename = pdfName?.length > 100 ? pdfName?.slice(0, 100) : pdfName;
@@ -258,6 +288,7 @@ async function sendCompletedMail(obj) {
html: body,
mailProvider: obj.mailProvider,
bcc: updatedBcc?.length > 0 ? updatedBcc : '',
cc: Cc?.length > 0 ? Cc : '',
certificatePath: `./exports/signed_certificate_${doc.objectId}.pdf`,
filename: docName,
};
@@ -360,11 +391,14 @@ async function PDF(req) {
const isCustomMail = req.params.isCustomCompletionMail || false;
const mailProvider = req.params.mailProvider || '';
const sign = req.params.signature || '';
const auditActivity = 'Signed';
const publicUrl = req.headers.public_url;
// below bode is used to get info of docId
const docQuery = new Parse.Query('contracts_Document');
docQuery.include('ExtUserPtr,Signers,ExtUserPtr.TenantId,Bcc,CreatedBy');
docQuery.include('ExtUserPtr,Signers,ExtUserPtr.TenantId,Bcc,Cc,CreatedBy');
docQuery.equalTo('objectId', docId);
docQuery.notEqualTo('IsDeclined', true);
docQuery.notEqualTo('IsArchive', true);
const resDoc = await docQuery.first({ useMasterKey: true });
if (!resDoc) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found.');
@@ -408,7 +442,7 @@ async function PDF(req) {
const P12Buffer = Buffer.from(pfxFile, 'base64');
fs.writeFileSync(pfxname, P12Buffer);
const UserPtr = { __type: 'Pointer', className: className, objectId: signUser.objectId };
const obj = { UserPtr: UserPtr, SignedUrl: '', Activity: 'Signed', ipAddress: userIP };
const obj = { UserPtr: UserPtr, SignedUrl: '', Activity: auditActivity, ipAddress: userIP };
let updateAuditTrail;
if (_resDoc.AuditTrail && _resDoc.AuditTrail.length > 0) {
updateAuditTrail = [..._resDoc.AuditTrail, obj];
@@ -416,14 +450,17 @@ async function PDF(req) {
updateAuditTrail = [obj];
}
const auditTrail = updateAuditTrail.filter(x => x.Activity === 'Signed');
// Both Signed and Approved entries count toward completion. Only
// signer/approver placeholders are counted; viewers and prefill are
// excluded.
const auditTrail = updateAuditTrail.filter(x => COMPLETION_ACTIVITIES.includes(x.Activity));
let isCompleted = false;
if (_resDoc.Signers && _resDoc.Signers.length > 0) {
const removePrefill =
_resDoc?.Placeholders?.length > 0 &&
_resDoc?.Placeholders?.filter(x => x?.Role !== 'prefill');
if (auditTrail.length === removePrefill?.length) {
// if (auditTrail.length === _resDoc.Signers.length) {
const completionRelevant =
_resDoc?.Placeholders?.length > 0
? _resDoc.Placeholders.filter(isCompletionRelevant)
: [];
if (auditTrail.length >= completionRelevant.length && completionRelevant.length > 0) {
isCompleted = true;
}
} else {
@@ -476,7 +513,8 @@ async function PDF(req) {
_resDoc, // auditTrail, signers, etc data
className, // className based on flow
sign, // sign base64
isCompleted ? documentHash : undefined
isCompleted ? documentHash : undefined,
auditActivity
);
sendNotifyMail(_resDoc, signUser, mailProvider, publicUrl);
saveFileUsage(pdfSize, data.imageUrl, _resDoc?.CreatedBy?.objectId);
@@ -1,3 +1,5 @@
import { setDocumentCount } from '../../utils/CountUtils.js';
export default async function recreateDocument(request) {
const { docId } = request.params;
if (!docId) {
@@ -10,6 +12,16 @@ export default async function recreateDocument(request) {
try {
const docQuery = new Parse.Query('contracts_Document');
docQuery.equalTo('objectId', docId);
docQuery.notEqualTo('IsArchive', true);
docQuery.exclude([
'DocSentAt',
'SignedUrl',
'AuditTrail',
'DeclineBy',
'DeclineReason',
'DocumentHash',
'CertificateUrl',
]);
const doc = await docQuery.first({ useMasterKey: true });
if (!doc) {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Document not found');
@@ -18,11 +30,27 @@ export default async function recreateDocument(request) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Signyourself Document not allowed');
}
const _docRes = doc?.toJSON();
const { objectId, SignedUrl, AuditTrail, ACL, DeclineBy, DeclineReason, ...docRes } = _docRes;
const { ACL, objectId, ...docRes } = _docRes;
const createDoc = new Parse.Object('contracts_Document');
Object.entries(docRes).forEach(([key, value]) => {
if (key === 'IsDeclined' || key === 'IsCompleted') {
createDoc.set(key, false);
} else if (key === 'Placeholders') {
const placeHolders = value.map(signer => ({
...signer,
placeHolder: (signer.placeHolder || []).map(page => ({
...page,
pos: (page.pos || []).map(widget => {
const { SignUrl, ...rest } = widget;
return {
...rest,
type: widget.type === 'text' ? 'text input' : widget.type,
options: { ...widget.options, response: '', defaultValue: '' },
};
}),
})),
}));
createDoc.set(key, placeHolders);
} else {
createDoc.set(key, value);
}
@@ -31,6 +59,7 @@ export default async function recreateDocument(request) {
const createDocRes = await createDoc.save(null, { useMasterKey: true });
// console.log('createDocRes', createDocRes);
const newDoc = JSON.parse(JSON.stringify(createDocRes));
setDocumentCount(_docRes?.ExtUserPtr?.objectId);
return { objectId: newDoc.objectId, createdAt: newDoc.createdAt, updatedAt: newDoc.updatedAt };
} catch (err) {
console.log('err in recreate document', err);
@@ -1,4 +1,5 @@
const randomId = () => Math.floor(1000 + Math.random() * 9000);
import { randomId } from '../../Utils.js';
export default async function saveAsTemplate(request) {
const docId = request.params.docId;
const Ip = request?.headers?.['x-real-ip'] || '';
@@ -23,6 +24,7 @@ export default async function saveAsTemplate(request) {
templateCls.set('Description', _docRes?.Description);
templateCls.set('OriginIp', Ip);
templateCls.set('SendinOrder', _docRes?.SendinOrder || false);
templateCls.set('SendInOrderStrict', _docRes?.SendInOrderStrict || false);
templateCls.set('AutomaticReminders', _docRes?.AutomaticReminders || false);
templateCls.set('ExtUserPtr', _docRes?.ExtUserPtr);
templateCls.set('CreatedBy', _docRes?.CreatedBy);
@@ -91,18 +93,27 @@ export default async function saveAsTemplate(request) {
pos: (page.pos || []).map(widget => {
// if there is a defaultValue in options, zero it out
if (widget.options && widget.options.defaultValue !== undefined) {
const { SignUrl, ...rest } = widget;
return {
...widget,
signatureType: '',
...rest,
type: widget.type === 'text' ? 'text input' : widget.type,
...(widget.type === 'signature' ? { signatureType: '' } : {}),
options: {
...widget.options,
defaultValue: '',
response: '',
...(widget?.options?.isReadOnly ? { isReadOnly: false } : {}),
},
}; // reset only the value
}
// otherwise, return the widget unchanged
return widget;
const { SignUrl, ...rest } = widget;
return {
...rest,
type: widget.type === 'text' ? 'text input' : widget.type,
...(widget.type === 'signature' ? { signatureType: '' } : {}),
options: { ...widget.options, response: '' },
};
}),
})),
}));
@@ -116,6 +127,9 @@ export default async function saveAsTemplate(request) {
if (_docRes?.Bcc?.length > 0) {
templateCls.set('Bcc', _docRes?.Bcc);
}
if (_docRes?.Cc?.length > 0) {
templateCls.set('Cc', _docRes?.Cc);
}
if (_docRes?.PenColors?.length > 0) {
templateCls.set('PenColors', _docRes?.PenColors);
}
@@ -46,6 +46,7 @@ const makeEmail = async (
url,
pdfName,
bcc,
cc,
filename,
certificatePath,
replyto,
@@ -54,6 +55,7 @@ const makeEmail = async (
const htmlContent = html;
const boundary = 'boundary_' + Date.now().toString(16);
const bccHeader = bcc && bcc.length > 0 ? `BCC: ${bcc.join(',')}\n` : ''; // Construct BCC header if provided
const ccHeader = cc && cc.length > 0 ? `CC: ${cc.join(',')}\n` : ''; // Construct CC header if provided
const replyToHeader = replyto ? `Reply-To: ${replyto}\n` : ''; // Construct Reply-To header if provided
let str;
@@ -137,6 +139,7 @@ const makeEmail = async (
`To: ${to}\n`,
`From: ${from}\n`,
bccHeader,
ccHeader,
replyToHeader,
`Subject: ${subject}\n\n`,
'--' + boundary + '\n',
@@ -154,6 +157,7 @@ const makeEmail = async (
`To: ${to}\n`,
`From: ${from}\n`,
bccHeader,
ccHeader,
replyToHeader,
`Subject: ${subject}\n\n`,
'--' + boundary + '\n',
@@ -168,8 +172,19 @@ const makeEmail = async (
return encodedMail;
};
export default async function sendMailGmailProvider(_extRes, template) {
const { sender, receiver, subject, html, url, pdfName, bcc, filename, certificatePath, replyto } =
template;
const {
sender,
receiver,
subject,
html,
url,
pdfName,
bcc,
cc,
filename,
certificatePath,
replyto,
} = template;
if (_extRes) {
let refresh_token = '';
@@ -192,6 +207,7 @@ export default async function sendMailGmailProvider(_extRes, template) {
url,
pdfName,
bcc,
cc,
filename,
certificatePath,
replyto,
@@ -222,7 +238,8 @@ export default async function sendMailGmailProvider(_extRes, template) {
}
return { code: 200, message: 'Email sent successfully' };
} catch (error) {
console.error('Error sending email:', error);
const message = error?.response?.data || error?.message || 'Unknown error';
console.error('Error sending email:', message);
return { code: 500, message: 'Failed to send email ' + error };
}
}
@@ -21,8 +21,8 @@ async function sendMailProvider(params) {
const reportMsg = `<p style="font-size: 13px; color:grey; text-align: center;">If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href="mailto:complaints@opensignlabs.com?subject=Spam%20report%20for%20user%20ID%20${extUserId}&body=Hello%20Support%20Team%2C%0D%0A%0D%0AI%E2%80%99m%20reporting%20spam%20activity%20coming%20from%20a%20sender%20using%20your%20platform.%0D%0A%0D%0AThe%20messages%20I%20received%20appear%20unsolicited%20and%20suspicious.%20The%20user%20ID%20associated%20with%20the%20emails%20is%3A%20${extUserId}.%20Please%20investigate%20this%20account%20and%20take%20appropriate%20action%20to%20prevent%20further%20abuse.%0D%0A%0D%0AIf%20you%20need%20additional%20details%2C%20I%E2%80%99m%20happy%20to%20provide%20the%20original%20email%20headers%20or%20screenshots.%0D%0A%0D%0AThank%20you%20for%20looking%20into%20this.%0D%0A%0D%0ABest%20regards%2C%0D%0A%5BYour%20Name%5D">here</a>.</p>`;
const mailgunApiKey = process.env.MAILGUN_API_KEY;
let transporterSMTP;
try {
let transporterSMTP;
let mailgunClient;
let mailgunDomain;
if (smtpenable) {
@@ -143,6 +143,7 @@ async function sendMailProvider(params) {
attachments: smtpenable ? attachment : undefined,
attachment: smtpenable ? undefined : attachment,
bcc: params.bcc ? params.bcc : undefined,
cc: params.cc ? params.cc : undefined,
replyTo: replyto ? replyto : undefined,
};
const cleanupPaths = [
@@ -193,6 +194,7 @@ async function sendMailProvider(params) {
text: params.text || 'mail',
html: params?.html ? params.html + reportMsg : '',
bcc: params.bcc ? params.bcc : undefined,
cc: params.cc ? params.cc : undefined,
replyTo: replyto ? replyto : undefined,
};
@@ -225,6 +227,10 @@ async function sendMailProvider(params) {
if (err) {
return { status: 'error' };
}
} finally {
if (transporterSMTP) {
transporterSMTP?.close?.();
}
}
}
@@ -1,18 +1,15 @@
import fs from 'node:fs';
import https from 'https';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
import { appName, smtpenable, smtpsecure, updateMailCount } from '../../Utils.js';
import { createTransport } from 'nodemailer';
import axios from 'axios';
async function sendMailProvider(req) {
const app = appName;
const extUserId = req.params?.extUserId || '';
const reportMsg = `<p style="font-size: 13px; color:grey; text-align: center;">If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href="mailto:complaints@opensignlabs.com?subject=Spam%20report%20for%20user%20ID%20${extUserId}&body=Hello%20Support%20Team%2C%0D%0A%0D%0AI%E2%80%99m%20reporting%20spam%20activity%20coming%20from%20a%20sender%20using%20your%20platform.%0D%0A%0D%0AThe%20messages%20I%20received%20appear%20unsolicited%20and%20suspicious.%20The%20user%20ID%20associated%20with%20the%20emails%20is%3A%20${extUserId}.%20Please%20investigate%20this%20account%20and%20take%20appropriate%20action%20to%20prevent%20further%20abuse.%0D%0A%0D%0AIf%20you%20need%20additional%20details%2C%20I%E2%80%99m%20happy%20to%20provide%20the%20original%20email%20headers%20or%20screenshots.%0D%0A%0D%0AThank%20you%20for%20looking%20into%20this.%0D%0A%0D%0ABest%20regards%2C%0D%0A%5BYour%20Name%5D">here</a>.</p>`;
const mailgunApiKey = process.env.MAILGUN_API_KEY;
let transporterSMTP;
try {
let transporterSMTP;
let mailgunClient;
let mailgunDomain;
if (smtpenable) {
@@ -51,6 +48,7 @@ async function sendMailProvider(req) {
text: req.params.text || 'mail',
html: req.params?.html ? req.params.html + reportMsg : '',
bcc: req.params.bcc ? req.params.bcc : undefined,
cc: req.params.cc ? req.params.cc : undefined,
replyTo: replyto ? replyto : undefined,
};
@@ -82,6 +80,10 @@ async function sendMailProvider(req) {
if (err) {
return { status: 'error' };
}
} finally {
if (transporterSMTP) {
transporterSMTP?.close?.();
}
}
}
@@ -0,0 +1,94 @@
import formData from 'form-data';
import Mailgun from 'mailgun.js';
import { appName, smtpenable, smtpsecure, updateMailCount } from '../../Utils.js';
import { createTransport } from 'nodemailer';
async function sendMailProvider(req) {
const app = appName;
const extUserId = req.params?.extUserId || '';
const reportMsg = `<p style="font-size: 13px; color:grey; text-align: center;">If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href="mailto:complaints@opensignlabs.com?subject=Spam%20report%20for%20user%20ID%20${extUserId}&body=Hello%20Support%20Team%2C%0D%0A%0D%0AI%E2%80%99m%20reporting%20spam%20activity%20coming%20from%20a%20sender%20using%20your%20platform.%0D%0A%0D%0AThe%20messages%20I%20received%20appear%20unsolicited%20and%20suspicious.%20The%20user%20ID%20associated%20with%20the%20emails%20is%3A%20${extUserId}.%20Please%20investigate%20this%20account%20and%20take%20appropriate%20action%20to%20prevent%20further%20abuse.%0D%0A%0D%0AIf%20you%20need%20additional%20details%2C%20I%E2%80%99m%20happy%20to%20provide%20the%20original%20email%20headers%20or%20screenshots.%0D%0A%0D%0AThank%20you%20for%20looking%20into%20this.%0D%0A%0D%0ABest%20regards%2C%0D%0A%5BYour%20Name%5D">here</a>.</p>`;
const mailgunApiKey = process.env.MAILGUN_API_KEY;
let transporterSMTP;
try {
let mailgunClient;
let mailgunDomain;
if (smtpenable) {
let transporterConfig = {
host: process.env.SMTP_HOST,
port: process.env.SMTP_PORT || 465,
secure: smtpsecure,
};
// ✅ Add auth only if BOTH username & password exist
const smtpUser = process.env.SMTP_USERNAME;
const smtpPass = process.env.SMTP_PASS;
if (smtpUser && smtpPass) {
transporterConfig.auth = {
user: process.env.SMTP_USERNAME ? process.env.SMTP_USERNAME : process.env.SMTP_USER_EMAIL,
pass: smtpPass,
};
}
transporterSMTP = createTransport(transporterConfig);
} else {
if (mailgunApiKey) {
const mailgun = new Mailgun(formData);
mailgunClient = mailgun.client({ username: 'api', key: mailgunApiKey });
mailgunDomain = process.env.MAILGUN_DOMAIN;
}
}
const from = req.params.from || '';
const mailsender = smtpenable ? process.env.SMTP_USER_EMAIL : process.env.MAILGUN_SENDER;
const replyto = req.params?.replyto || '';
const messageParams = {
from: from + ' <' + mailsender + '>',
to: req.params.recipient,
subject: req.params.subject,
text: req.params.text || 'mail',
html: req.params?.html ? req.params.html + reportMsg : '',
bcc: req.params.bcc ? req.params.bcc : undefined,
replyTo: replyto ? replyto : undefined,
};
if (transporterSMTP) {
const res = await transporterSMTP.sendMail(messageParams);
console.log('smtp transporter res: ', res?.response);
if (!res.err) {
if (extUserId) {
await updateMailCount(extUserId);
}
return { status: 'success' };
}
} else {
if (mailgunApiKey) {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('mailgun res: ', res?.status);
if (res.status === 200) {
if (extUserId) {
await updateMailCount(extUserId);
}
return { status: 'success' };
}
} else {
return { status: 'error' };
}
}
} catch (err) {
console.log(`sendSystemMail Error: ${err}`);
if (err) {
return { status: 'error' };
}
} finally {
if (transporterSMTP) {
transporterSMTP?.close?.();
}
}
}
async function sendSystemMail(req) {
const nonCustomMail = await sendMailProvider(req);
return nonCustomMail;
}
export default sendSystemMail;
@@ -5,6 +5,7 @@ const APPID = serverAppId;
const masterKEY = process.env.MASTER_KEY;
async function saveUser(userDetails) {
const normalizedEmail = normalizeEmail(userDetails.email.toLowerCase().replace(/\s/g, ''));
const userQuery = new Parse.Query(Parse.User);
userQuery.equalTo('username', userDetails.email);
const userRes = await userQuery.first({ useMasterKey: true });
@@ -31,6 +32,8 @@ async function saveUser(userDetails) {
user.set('username', userDetails.email);
user.set('password', userDetails.password);
user.set('email', userDetails?.email?.toLowerCase()?.replace(/\s/g, ''));
user.set('normalizedEmail', normalizedEmail);
if (userDetails?.phone) {
user.set('phone', userDetails.phone);
}