Revert "v2.39.0 (#2180)"

This reverts commit 1e228c86c3.
This commit is contained in:
Raktima
2026-05-25 17:24:36 +05:30
committed by GitHub
parent 1e228c86c3
commit 7b4d0bf052
92 changed files with 5372 additions and 8680 deletions
@@ -1,7 +1,7 @@
async function DocumentAftersave(request) {
try {
if (!request.original) {
console.log('new entry is insert in contracts_Document ', request?.object?.id);
console.log('new entry is insert in contracts_Document');
const obj = request.object;
const objId = obj?.id;
const createdAt = obj?.get?.('createdAt');
@@ -26,8 +26,6 @@ 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');
@@ -64,7 +62,6 @@ 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', request?.object?.id);
console.log('new entry is insert in contracts_Template');
const obj = request.object;
const objId = obj?.id;
const ip = request?.headers?.['x-real-ip'] || '';
@@ -3,7 +3,6 @@ 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 = [];
@@ -51,6 +50,9 @@ 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
@@ -81,7 +83,7 @@ async function sendOwnerSummaryEmail({
html,
};
await sendSystemMail({ params });
await axios.post(url, params, { headers });
} catch (e) {
console.log('batchdoc Failed to send owner summary email:', e?.message || e);
}
@@ -90,7 +92,7 @@ async function sendOwnerSummaryEmail({
async function deductcount(docsCount, extUserId) {
try {
if (extUserId) {
setDocumentCount(extUserId, docsCount);
setDocumentCount(extUserId);
}
} catch (err) {
console.log('batchdoc deductcount error: ', err);
@@ -228,7 +230,6 @@ 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,
@@ -271,7 +272,6 @@ 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,6 +303,7 @@ 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 };
}
@@ -1,99 +0,0 @@
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,7 +38,6 @@ 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);
@@ -78,9 +77,6 @@ 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,5 +1,13 @@
import { appName } from '../../Utils.js';
import sendSystemMail from './sendSystemMail.js';
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,
};
async function sendDeclineMail(doc, publicUrl, userId, reason) {
try {
@@ -38,7 +46,7 @@ async function sendDeclineMail(doc, publicUrl, userId, reason) {
pdfName: pdfName,
html: body,
};
await sendSystemMail({ params });
await axios.post(serverUrl + '/functions/sendmailv3', params, { headers });
} catch (err) {
console.log('err in sendnotifymail', err);
}
@@ -55,21 +63,16 @@ 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 });
if (!isCreator) {
sendDeclineMail(_doc, publicUrl, userId, reason);
}
sendDeclineMail(_doc, publicUrl, userId, reason);
return 'document declined';
} else {
if (!request?.user) {
@@ -79,10 +82,7 @@ export default async function declinedocument(request) {
updateDoc.set('DeclineReason', reason);
updateDoc.set('DeclineBy', declineBy);
await updateDoc.save(null, { useMasterKey: true });
const isCreator = _doc?.CreatedBy?.objectId === request?.user?.id;
if (!isCreator) {
sendDeclineMail(_doc, publicUrl, userId, reason);
}
sendDeclineMail(_doc, publicUrl, userId, reason);
return 'document declined';
}
} else {
@@ -17,6 +17,11 @@ 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,13 +3,6 @@ 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;
@@ -19,9 +12,7 @@ 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('./images/logo.png').buffer;
const naSignUrl = fs.readFileSync('./images/na_sign.png').buffer;
const nasign = await pdfDoc.embedPng(naSignUrl);
const pngUrl = fs.readFileSync('./logo.png').buffer;
const pngImage = await pdfDoc.embedPng(pngUrl);
const page = pdfDoc.addPage();
const { width, height } = page.getSize();
@@ -38,7 +29,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 = formatDateStr(completedAt, DateFormat, timezone, Is12Hr);
const completedAtperTimezone = formatDateTime(completedAt, DateFormat, timezone, Is12Hr);
const completedUTCtime = completedAtperTimezone;
const signersCount = docDetails?.Signers?.length || 1;
const generateAt = docDetails?.completedAt ? new Date(docDetails?.completedAt) : new Date();
@@ -52,19 +43,9 @@ export default async function GenerateCertificate(docDetails) {
const company = docDetails?.ExtUserPtr?.Company || '';
const documentHash = docDetails?.DocumentHash || '';
const createdAt = docDetails?.DocSentAt?.iso || docDetails.createdAt;
const createdAtperTimezone = formatDateStr(createdAt, DateFormat, timezone, Is12Hr);
const createdAtperTimezone = formatDateTime(createdAt, DateFormat, timezone, Is12Hr);
const IsEnableOTP = docDetails?.IsEnableOTP || false;
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 filteredaudit = docDetails?.AuditTrail?.filter(x => x?.UserPtr?.objectId);
const auditTrail =
docDetails?.Signers?.length > 0
? filteredaudit?.map(x => {
@@ -75,7 +56,6 @@ export default async function GenerateCertificate(docDetails) {
SignedOn: x?.SignedOn || generatedUTCTime,
ViewedOn: x?.ViewedOn || x?.SignedOn || generatedUTCTime,
Signature: x?.Signature || '',
_signedOnTs: toTs(x?.SignedOn),
};
})
: [
@@ -85,12 +65,8 @@ 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';
@@ -327,63 +303,24 @@ export default async function GenerateCertificate(docDetails) {
let yPosition7 = yPosition6 - 20;
let yPosition8 = yPosition7 - 35;
// 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, {
auditTrail.slice(0, 3).forEach(async (x, i) => {
const embedPng = x.Signature ? await pdfDoc.embedPng(x.Signature) : '';
page.drawText(`Signer ${i + 1}`, {
x: 30,
y: yPosition1,
size: subtitle,
font: timesRomanFont,
color: titleColor,
});
currentPage.drawText('Name :', {
page.drawText('Name :', {
x: 30,
y: yPosition2,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
currentPage.drawText(x?.Name || '', {
page.drawText(x?.Name, {
x: 75,
y: yPosition2,
size: signertext,
@@ -392,14 +329,14 @@ export default async function GenerateCertificate(docDetails) {
});
if (IsEnableOTP) {
currentPage.drawText('Security level :', {
page.drawText('Security level :', {
x: half + 120,
y: yPosition2,
size: timeText,
font: timesRomanFont,
color: textKeyColor,
});
currentPage.drawText('Email, OTP Auth', {
page.drawText('Email, OTP Auth', {
x: half + 190,
y: yPosition2,
size: timeText,
@@ -408,14 +345,15 @@ export default async function GenerateCertificate(docDetails) {
});
}
currentPage.drawText('Email :', {
page.drawText('Email :', {
x: 30,
y: yPosition3,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
currentPage.drawText(x?.Email || '', {
page.drawText(x?.Email, {
x: 75,
y: yPosition3,
size: signertext,
@@ -423,14 +361,15 @@ export default async function GenerateCertificate(docDetails) {
color: textValueColor,
});
currentPage.drawText('Viewed on :', {
page.drawText('Viewed on :', {
x: 30,
y: yPosition4,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
currentPage.drawText(`${formatDateStr(x?.ViewedOn, DateFormat, timezone, Is12Hr)}`, {
page.drawText(`${formatDateTime(x.ViewedOn, DateFormat, timezone, Is12Hr)}`, {
x: 97,
y: yPosition4,
size: signertext,
@@ -438,30 +377,31 @@ export default async function GenerateCertificate(docDetails) {
color: textValueColor,
});
currentPage.drawText(signedOnLabel, {
page.drawText('Signed on :', {
x: 30,
y: yPosition5,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
const signedOnValueX = 30 + timesRomanFont.widthOfTextAtSize(signedOnLabel, signertext) + 5;
currentPage.drawText(`${formatDateStr(x?.SignedOn, DateFormat, timezone, Is12Hr)}`, {
x: signedOnValueX,
page.drawText(`${formatDateTime(x.SignedOn, DateFormat, timezone, Is12Hr)}`, {
x: 95,
y: yPosition5,
size: signertext,
font: timesRomanFont,
color: textValueColor,
});
currentPage.drawText('IP address :', {
page.drawText('IP address :', {
x: 30,
y: yPosition6,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
currentPage.drawText(x?.ipAddress || '', {
page.drawText(x?.ipAddress, {
x: 95,
y: yPosition6,
size: signertext,
@@ -469,14 +409,15 @@ export default async function GenerateCertificate(docDetails) {
color: textValueColor,
});
currentPage.drawText('Signature :', {
page.drawText('Signature :', {
x: 30,
y: yPosition7,
size: signertext,
font: timesRomanFont,
color: textKeyColor,
});
currentPage.drawRectangle({
page.drawRectangle({
x: 98,
y: yPosition7 - 30,
width: 104,
@@ -485,14 +426,14 @@ export default async function GenerateCertificate(docDetails) {
borderWidth: 1,
});
if (embedPng) {
currentPage.drawImage(embedPng, {
page.drawImage(embedPng, {
x: 100,
y: yPosition7 - 27,
width: 100,
height: 40,
});
}
currentPage.drawLine({
page.drawLine({
start: { x: 30, y: yPosition8 },
end: { x: width - 30, y: yPosition8 },
color: rgb(0.12, 0.12, 0.12),
@@ -507,6 +448,185 @@ 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,22 +17,6 @@ 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;
@@ -78,24 +62,13 @@ 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,
activity
) {
async function updateDoc(docId, url, userId, ipAddress, data, className, sign, documentHash) {
try {
const UserPtr = { __type: 'Pointer', className: className, objectId: userId };
const auditActivity = 'Signed';
const obj = {
UserPtr: UserPtr,
SignedUrl: url,
Activity: auditActivity,
Activity: 'Signed',
ipAddress: ipAddress,
SignedOn: new Date(),
Signature: sign,
@@ -115,14 +88,13 @@ async function updateDoc(
updateAuditTrail = [obj];
}
// 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));
const auditTrail = updateAuditTrail.filter(x => x.Activity === 'Signed');
let isCompleted = false;
if (data.Signers && data.Signers.length > 0) {
const completionRelevant =
data.Placeholders?.length > 0 ? data.Placeholders.filter(isCompletionRelevant) : [];
if (auditTrail.length >= completionRelevant.length && completionRelevant.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) {
isCompleted = true;
}
} else {
@@ -152,11 +124,10 @@ 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 => COMPLETION_ACTIVITIES.includes(x.Activity))?.length || 0;
const completionRelevant =
doc?.Placeholders?.length > 0 ? doc.Placeholders.filter(isCompletionRelevant) : [];
const signersCount = completionRelevant?.length;
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 remainingSign = signersCount - auditTrailCount;
if (remainingSign > 1 && doc?.NotifyOnSignatures) {
const sender = doc.ExtUserPtr;
@@ -183,7 +154,7 @@ async function sendNotifyMail(doc, signUser, mailProvider, publicUrl) {
html: body,
mailProvider: mailProvider,
};
await sendSystemMail({ params });
await axios.post(serverUrl + '/functions/sendmailv3', params, { headers });
}
} catch (err) {
console.log('err in sendnotifymail', err);
@@ -268,7 +239,6 @@ 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;
@@ -288,7 +258,6 @@ 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,
};
@@ -391,14 +360,11 @@ 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,Cc,CreatedBy');
docQuery.include('ExtUserPtr,Signers,ExtUserPtr.TenantId,Bcc,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.');
@@ -425,27 +391,7 @@ async function PDF(req) {
className = 'contracts_Users';
signUser = _resDoc.ExtUserPtr;
}
// Strict-order gating: when both `SendinOrder` and `SendInOrderStrict`
// are enabled the document creator wants the signing flow locked to a
// strict sequence — a signer/approver may only act once every previous
// signer/approver placeholder has a Signed/Approved audit entry. We
// skip this check entirely for the document owner (className=Users)
// because owners never sign through this path.
if (reqUserId && _resDoc?.SendinOrder === true && _resDoc?.SendInOrderStrict === true) {
const placeholders = Array.isArray(_resDoc?.Placeholders)
? _resDoc.Placeholders.filter(p => p?.Role !== 'prefill')
: [];
const myIdx = findPlaceholderIndex(placeholders, reqUserId);
if (myIdx > 0) {
const pendingId = findPendingPriorSigner(placeholders, myIdx, _resDoc?.AuditTrail);
if (pendingId) {
throw new Parse.Error(
Parse.Error.OPERATION_FORBIDDEN,
'Strict signing order is enabled — please wait for the previous signers to complete their action before signing.'
);
}
}
}
const username = signUser.Name;
const userEmail = signUser.Email;
if (req.params.pdfFile) {
@@ -462,7 +408,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: auditActivity, ipAddress: userIP };
const obj = { UserPtr: UserPtr, SignedUrl: '', Activity: 'Signed', ipAddress: userIP };
let updateAuditTrail;
if (_resDoc.AuditTrail && _resDoc.AuditTrail.length > 0) {
updateAuditTrail = [..._resDoc.AuditTrail, obj];
@@ -470,17 +416,14 @@ async function PDF(req) {
updateAuditTrail = [obj];
}
// 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));
const auditTrail = updateAuditTrail.filter(x => x.Activity === 'Signed');
let isCompleted = false;
if (_resDoc.Signers && _resDoc.Signers.length > 0) {
const completionRelevant =
_resDoc?.Placeholders?.length > 0
? _resDoc.Placeholders.filter(isCompletionRelevant)
: [];
if (auditTrail.length >= completionRelevant.length && completionRelevant.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) {
isCompleted = true;
}
} else {
@@ -533,8 +476,7 @@ async function PDF(req) {
_resDoc, // auditTrail, signers, etc data
className, // className based on flow
sign, // sign base64
isCompleted ? documentHash : undefined,
auditActivity
isCompleted ? documentHash : undefined
);
sendNotifyMail(_resDoc, signUser, mailProvider, publicUrl);
saveFileUsage(pdfSize, data.imageUrl, _resDoc?.CreatedBy?.objectId);
@@ -1,5 +1,3 @@
import { setDocumentCount } from '../../utils/CountUtils.js';
export default async function recreateDocument(request) {
const { docId } = request.params;
if (!docId) {
@@ -12,16 +10,6 @@ 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');
@@ -30,27 +18,11 @@ export default async function recreateDocument(request) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'Signyourself Document not allowed');
}
const _docRes = doc?.toJSON();
const { ACL, objectId, ...docRes } = _docRes;
const { objectId, SignedUrl, AuditTrail, ACL, DeclineBy, DeclineReason, ...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);
}
@@ -59,7 +31,6 @@ 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,5 +1,4 @@
import { randomId } from '../../Utils.js';
const randomId = () => Math.floor(1000 + Math.random() * 9000);
export default async function saveAsTemplate(request) {
const docId = request.params.docId;
const Ip = request?.headers?.['x-real-ip'] || '';
@@ -24,7 +23,6 @@ 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);
@@ -93,27 +91,18 @@ 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 {
...rest,
type: widget.type === 'text' ? 'text input' : widget.type,
...(widget.type === 'signature' ? { signatureType: '' } : {}),
...widget,
signatureType: '',
options: {
...widget.options,
defaultValue: '',
response: '',
...(widget?.options?.isReadOnly ? { isReadOnly: false } : {}),
},
}; // reset only the value
}
// otherwise, return the widget unchanged
const { SignUrl, ...rest } = widget;
return {
...rest,
type: widget.type === 'text' ? 'text input' : widget.type,
...(widget.type === 'signature' ? { signatureType: '' } : {}),
options: { ...widget.options, response: '' },
};
return widget;
}),
})),
}));
@@ -127,9 +116,6 @@ 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,7 +46,6 @@ const makeEmail = async (
url,
pdfName,
bcc,
cc,
filename,
certificatePath,
replyto,
@@ -55,7 +54,6 @@ 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;
@@ -139,7 +137,6 @@ const makeEmail = async (
`To: ${to}\n`,
`From: ${from}\n`,
bccHeader,
ccHeader,
replyToHeader,
`Subject: ${subject}\n\n`,
'--' + boundary + '\n',
@@ -157,7 +154,6 @@ const makeEmail = async (
`To: ${to}\n`,
`From: ${from}\n`,
bccHeader,
ccHeader,
replyToHeader,
`Subject: ${subject}\n\n`,
'--' + boundary + '\n',
@@ -172,19 +168,8 @@ const makeEmail = async (
return encodedMail;
};
export default async function sendMailGmailProvider(_extRes, template) {
const {
sender,
receiver,
subject,
html,
url,
pdfName,
bcc,
cc,
filename,
certificatePath,
replyto,
} = template;
const { sender, receiver, subject, html, url, pdfName, bcc, filename, certificatePath, replyto } =
template;
if (_extRes) {
let refresh_token = '';
@@ -207,7 +192,6 @@ export default async function sendMailGmailProvider(_extRes, template) {
url,
pdfName,
bcc,
cc,
filename,
certificatePath,
replyto,
@@ -238,8 +222,7 @@ export default async function sendMailGmailProvider(_extRes, template) {
}
return { code: 200, message: 'Email sent successfully' };
} catch (error) {
const message = error?.response?.data || error?.message || 'Unknown error';
console.error('Error sending email:', message);
console.error('Error sending email:', error);
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,7 +143,6 @@ 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 = [
@@ -194,7 +193,6 @@ 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,
};
@@ -227,10 +225,6 @@ async function sendMailProvider(params) {
if (err) {
return { status: 'error' };
}
} finally {
if (transporterSMTP) {
transporterSMTP?.close?.();
}
}
}
@@ -1,15 +1,18 @@
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) {
@@ -48,7 +51,6 @@ 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,
};
@@ -80,10 +82,6 @@ async function sendMailProvider(req) {
if (err) {
return { status: 'error' };
}
} finally {
if (transporterSMTP) {
transporterSMTP?.close?.();
}
}
}
@@ -1,94 +0,0 @@
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,7 +5,6 @@ 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 });
@@ -32,8 +31,6 @@ 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);
}