mirror of
https://github.com/OpenSignLabs/OpenSign.git
synced 2026-08-20 14:42:37 +02:00
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user