Merge pull request

This commit is contained in:
prafull-opensignlabs
2026-03-04 07:31:19 +00:00
parent 2e2e535b70
commit acb0f3083c
158 changed files with 40172 additions and 1456 deletions
@@ -10,37 +10,28 @@ async function DocumentAfterFind(request) {
const certificateUrl = obj.get('CertificateUrl') && obj.get('CertificateUrl');
const isPrefillExist = obj?.get('Placeholders')?.some(x => x.Role === 'prefill');
const Placeholder = obj?.get('Placeholders') || [];
if (useLocal !== 'true') {
if (isPrefillExist) {
const updatedPlaceHolder = await handleValidImage(Placeholder);
obj.set('Placeholders', updatedPlaceHolder);
const shouldUsePresigned = useLocal !== 'true';
const isLocal = useLocal == 'true';
const resolveUrl = async rawUrl => {
if (!rawUrl) return rawUrl;
if (shouldUsePresigned) {
return await getPresignedUrl(rawUrl);
} else if (isLocal) {
return presignedlocalUrl(rawUrl);
}
if (SignedUrl) {
obj.set('SignedUrl', getPresignedUrl(SignedUrl));
}
if (Url) {
obj.set('URL', getPresignedUrl(Url));
}
if (certificateUrl) {
obj.set('CertificateUrl', getPresignedUrl(certificateUrl));
}
return [obj];
} else if (useLocal == 'true') {
if (isPrefillExist) {
const updatedPlaceHolder = await handleValidImage(Placeholder);
obj.set('Placeholders', updatedPlaceHolder);
}
if (SignedUrl) {
obj.set('SignedUrl', presignedlocalUrl(SignedUrl));
}
if (Url) {
obj.set('URL', presignedlocalUrl(Url));
}
if (certificateUrl) {
obj.set('CertificateUrl', presignedlocalUrl(certificateUrl));
}
return [obj];
};
if (isPrefillExist) {
const updatedPlaceHolder = await handleValidImage(Placeholder);
obj.set('Placeholders', updatedPlaceHolder);
}
if (SignedUrl) obj.set('SignedUrl', await resolveUrl(SignedUrl));
if (Url) obj.set('URL', await resolveUrl(Url));
if (certificateUrl) obj.set('CertificateUrl', await resolveUrl(certificateUrl));
return [obj];
}
}
}
@@ -36,7 +36,7 @@ export default async function forwardDoc(request) {
let mailRes;
for (let i = 0; i < recipients.length; i++) {
const logo = `<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>`;
const opurl = ` <a href='mailto:complaint@opensiglabs.com' target=_blank>here</a>`;
const themeColor = '#47a3ad';
let params = {
@@ -51,8 +51,7 @@ export default async function forwardDoc(request) {
`<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8'/></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'><div>` +
`${logo}</div><div style='padding:2px;font-family:system-ui;background-color:${themeColor}'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document Copy</p></div><div>` +
`<p style='padding:20px;font-family:system-ui;font-size:14px'>A copy of the document <strong>${docName}</strong> is attached to this email. Kindly download the document from the attachment.</p>` +
`</div></div><div><p>This is an automated email from ${TenantAppName}. For any queries regarding this email, please contact the sender ${replyTo} directly. ` +
`If you think this email is inappropriate or spam, you may file a complaints with ${TenantAppName}${opurl}.</p></div></div></body></html>`,
`</div></div><div><p>This is an automated email from ${TenantAppName}. For any queries regarding this email, please contact the sender ${replyTo} directly.</p></div></div></body></html>`,
};
mailRes = await axios.post(`${cloudServerUrl}/functions/sendmailv3`, params, {
headers: {
@@ -11,6 +11,7 @@ export default async function GetLogoByDomain(request) {
const updateRes = JSON.parse(JSON.stringify(res));
return {
logo: updateRes?.Logo,
favicon: updateRes?.Favicon || updateRes?.Logo,
appname: appName,
user: 'exist',
};
@@ -1,44 +1,28 @@
import { useLocal } from '../../Utils.js';
import getPresignedUrl, { presignedlocalUrl } from './getSignedUrl.js';
const resolveUrl = async rawUrl => {
const isLocal = useLocal == 'true';
const shouldUsePresigned = useLocal !== 'true';
if (!rawUrl) return rawUrl;
if (shouldUsePresigned) {
return await getPresignedUrl(rawUrl);
} else if (isLocal) {
return presignedlocalUrl(rawUrl);
}
};
async function SignatureAfterFind(request) {
if (useLocal !== 'true') {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const ImageURL = obj?.get('ImageURL') && obj?.get('ImageURL');
const Initials = obj?.get('Initials') && obj?.get('Initials');
const Stamp = obj?.get('Stamp') && obj?.get('Stamp');
if (ImageURL) {
obj.set('ImageURL', getPresignedUrl(ImageURL));
}
if (Initials) {
obj.set('Initials', getPresignedUrl(Initials));
}
if (Stamp) {
obj.set('Stamp', getPresignedUrl(Stamp));
}
return [obj];
}
}
} else if (useLocal == 'true') {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const ImageURL = obj?.get('ImageURL') && obj?.get('ImageURL');
const Initials = obj?.get('Initials') && obj?.get('Initials');
const Stamp = obj?.get('Stamp') && obj?.get('Stamp');
if (ImageURL) {
obj.set('ImageURL', presignedlocalUrl(ImageURL));
}
if (Initials) {
obj.set('Initials', presignedlocalUrl(Initials));
}
if (Stamp) {
obj.set('Stamp', presignedlocalUrl(Stamp));
}
return [obj];
}
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const ImageURL = obj?.get('ImageURL') && obj?.get('ImageURL');
const Initials = obj?.get('Initials') && obj?.get('Initials');
const Stamp = obj?.get('Stamp') && obj?.get('Stamp');
if (ImageURL) obj.set('ImageURL', await resolveUrl(ImageURL));
if (Initials) obj.set('Initials', await resolveUrl(Initials));
if (Stamp) obj.set('Stamp', await resolveUrl(Stamp));
return [obj];
}
}
}
@@ -10,37 +10,26 @@ async function TemplateAfterFind(request) {
const certificateUrl = obj.get('CertificateUrl') && obj?.get('CertificateUrl');
const isPrefillExist = obj?.get('Placeholders')?.some(x => x.Role === 'prefill');
const Placeholder = obj?.get('Placeholders') || [];
if (useLocal !== 'true') {
if (isPrefillExist) {
const updatedPlaceHolder = await handleValidImage(Placeholder);
obj.set('Placeholders', updatedPlaceHolder);
const shouldUsePresigned = useLocal !== 'true';
const isLocal = useLocal == 'true';
const resolveUrl = async rawUrl => {
if (!rawUrl) return rawUrl;
if (shouldUsePresigned) {
return await getPresignedUrl(rawUrl);
} else if (isLocal) {
return presignedlocalUrl(rawUrl);
}
if (SignedUrl) {
obj.set('SignedUrl', getPresignedUrl(SignedUrl));
}
if (Url) {
obj.set('URL', getPresignedUrl(Url));
}
if (certificateUrl) {
obj.set('CertificateUrl', getPresignedUrl(certificateUrl));
}
return [obj];
} else if (useLocal == 'true') {
if (isPrefillExist) {
const updatedPlaceHolder = await handleValidImage(Placeholder);
obj.set('Placeholders', updatedPlaceHolder);
}
if (SignedUrl) {
obj.set('SignedUrl', presignedlocalUrl(SignedUrl));
}
if (Url) {
obj.set('URL', presignedlocalUrl(Url));
}
if (certificateUrl) {
obj.set('CertificateUrl', presignedlocalUrl(certificateUrl));
}
return [obj];
};
if (isPrefillExist) {
const updatedPlaceHolder = await handleValidImage(Placeholder);
obj.set('Placeholders', updatedPlaceHolder);
}
if (SignedUrl) obj.set('SignedUrl', await resolveUrl(SignedUrl));
if (Url) obj.set('URL', await resolveUrl(Url));
if (certificateUrl) obj.set('CertificateUrl', await resolveUrl(certificateUrl));
return [obj];
}
}
}
@@ -1,28 +1,27 @@
import { useLocal } from '../../Utils.js';
import getPresignedUrl, { presignedlocalUrl } from './getSignedUrl.js';
const resolveUrl = async rawUrl => {
const isLocal = useLocal == 'true';
const shouldUsePresigned = useLocal !== 'true';
if (!rawUrl) return rawUrl;
if (shouldUsePresigned) {
return await getPresignedUrl(rawUrl);
} else if (isLocal) {
return presignedlocalUrl(rawUrl);
}
};
async function TenantAterFind(request) {
if (useLocal !== 'true') {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const Logo = obj?.get('Logo') && obj?.get('Logo');
if (Logo) {
obj.set('Logo', getPresignedUrl(Logo));
}
return [obj];
}
}
} else if (useLocal == 'true') {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const Logo = obj?.get('Logo') && obj?.get('Logo');
if (Logo) {
obj.set('Logo', presignedlocalUrl(Logo));
}
return [obj];
}
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const Logo = obj?.get('Logo') && obj?.get('Logo');
const Favicon = obj?.get('Favicon') && obj?.get('Favicon');
if (Logo) obj.set('Logo', await resolveUrl(Logo));
if (Favicon) obj.set('Favicon', await resolveUrl(Favicon));
return [obj];
}
}
}
@@ -1,28 +1,24 @@
import { useLocal } from '../../Utils.js';
import getPresignedUrl, { presignedlocalUrl } from './getSignedUrl.js';
const resolveUrl = async rawUrl => {
const isLocal = useLocal == 'true';
const shouldUsePresigned = useLocal !== 'true';
if (!rawUrl) return rawUrl;
if (shouldUsePresigned) {
return await getPresignedUrl(rawUrl);
} else if (isLocal) {
return presignedlocalUrl(rawUrl);
}
};
async function UserAfterFind(request) {
if (useLocal !== 'true') {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const ProfilePic = obj?.get('ProfilePic') && obj?.get('ProfilePic');
if (ProfilePic) {
obj.set('ProfilePic', getPresignedUrl(ProfilePic));
}
return [obj];
}
}
} else if (useLocal == 'true') {
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const ProfilePic = obj?.get('ProfilePic') && obj?.get('ProfilePic');
if (ProfilePic) {
obj.set('ProfilePic', presignedlocalUrl(ProfilePic));
}
return [obj];
}
if (request.objects.length === 1) {
if (request.objects) {
const obj = request.objects[0];
const ProfilePic = obj?.get('ProfilePic') && obj?.get('ProfilePic');
if (ProfilePic) obj.set('ProfilePic', await resolveUrl(ProfilePic));
return [obj];
}
}
}
@@ -2,19 +2,103 @@ import axios from 'axios';
import { cloudServerUrl, mailTemplate, replaceMailVaribles, serverAppId } from '../../Utils.js';
import { setDocumentCount } from '../../utils/CountUtils.js';
import crypto from 'crypto';
function chunkArray(arr, size) {
const out = [];
for (let i = 0; i < arr.length; i += size) out.push(arr.slice(i, i + size));
return out;
}
async function mapWithConcurrency(items, concurrency, fn) {
const results = new Array(items.length);
let idx = 0;
async function worker() {
while (true) {
const current = idx++;
if (current >= items.length) return;
results[current] = await fn(items[current], current);
}
}
const workers = [];
for (let i = 0; i < Math.min(concurrency, items.length); i++) {
workers.push(worker());
}
await Promise.all(workers);
return results;
}
function toBase64(str) {
return Buffer.from(str, 'utf8').toString('base64');
}
function uuid() {
return crypto.randomUUID ? crypto.randomUUID() : crypto.randomBytes(16).toString('hex');
}
const serverUrl = cloudServerUrl; //process.env.SERVER_URL;
const appId = serverAppId;
async function sendOwnerSummaryEmail({
ownerEmail,
ownerName,
total,
created,
failed,
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
? `<ul>${failedList
.slice(0, 50)
.map(f => `<li>#${f.index + 1}: ${String(f.error).slice(0, 200)}</li>`)
.join('')}</ul>
${failedList.length > 50 ? `<p>…and ${failedList.length - 50} more.</p>` : ''}`
: `<p>No failures.</p>`;
const html = `
<p>Hi ${ownerName || ''},</p>
<p>Your bulk send processing is complete.</p>
<p><b>Total requested:</b> ${total}<br/>
<b>Created:</b> ${created}<br/>
<b>Failed to create:</b> ${failed}</p>
<h4>Failure details</h4>
${failureHtml}
`;
const params = {
// keep provider selection consistent with your system; if you cant decide, omit it.
isbulksend: true,
recipient: ownerEmail,
subject,
from: ownerEmail, // or use a tenant/from address if required by your provider
replyto: ownerEmail,
html,
};
await axios.post(url, params, { headers });
} catch (e) {
console.log('batchdoc Failed to send owner summary email:', e?.message || e);
}
}
async function deductcount(docsCount, extUserId) {
try {
if (extUserId) {
setDocumentCount(extUserId);
}
} catch (err) {
console.log('Err in deduct in quick send', err);
console.log('batchdoc deductcount error: ', err);
}
}
async function sendMail(document, publicUrl) {
//sessionToken
const baseUrl = new URL(publicUrl);
const timeToCompleteDays = document?.TimeToCompleteDays || 15;
const ExpireDate = new Date(document.createdAt);
@@ -43,9 +127,9 @@ async function sendMail(document, publicUrl) {
let existSigner = {};
if (objectId) {
existSigner = document?.Signers?.find(user => user.objectId === objectId);
encodeBase64 = btoa(`${document.objectId}/${existSigner?.Email}/${objectId}`);
encodeBase64 = toBase64(`${document.objectId}/${existSigner?.Email}/${objectId}`);
} else {
encodeBase64 = btoa(`${document.objectId}/${signerMail[i].email}`);
encodeBase64 = toBase64(`${document.objectId}/${signerMail[i].email}`);
}
let signPdf = `${hostUrl}/login/${encodeBase64}`;
const orgName = document.ExtUserPtr.Company ? document.ExtUserPtr.Company : '';
@@ -91,139 +175,131 @@ async function sendMail(document, publicUrl) {
replyto: senderEmail || '',
html: replaceVar?.body ? replaceVar?.body : mailTemplate(mailparam).body,
};
const sendMail = await axios.post(url, params, { headers: headers });
// if (sendMail.data.result.status === 'success') {
// console.log('batch login mail sent');
// }
await axios.post(url, params, { headers: headers });
} catch (error) {
console.log('error', error);
console.log('batchdoc sendmail error: ', error);
}
}
}
async function batchQuery(userId, Documents, Ip, parseConfig, type, publicUrl) {
async function startBulkSendInBackground(userId, Documents, Ip, parseConfig, type, publicUrl) {
const BATCH_LIMIT = 50; // Parse batch limit (safe)
const DOC_MAIL_CONCURRENCY = 5;
// Find ext user
const extCls = new Parse.Query('contracts_Users');
extCls.equalTo('UserId', { __type: 'Pointer', className: '_User', objectId: userId });
const resExt = await extCls.first({ useMasterKey: true });
if (resExt) {
const _resExt = JSON.parse(JSON.stringify(resExt));
try {
const requests = Documents.map(x => {
const Signers = x.Signers;
const placeholders = x?.Placeholders?.filter(p => p?.Role !== 'prefill');
const allSigner = placeholders
?.map(
item => Signers?.find(e => item?.signerPtr?.objectId === e?.objectId) || item?.signerPtr
)
.filter(signer => Object.keys(signer).length > 0);
const date = new Date();
const isoDate = date.toISOString();
let Acl = { [x.CreatedBy.objectId]: { read: true, write: true } };
if (allSigner && allSigner.length > 0) {
allSigner.forEach(x => {
if (x?.CreatedBy?.objectId) {
const obj = { [x.CreatedBy.objectId]: { read: true, write: true } };
Acl = { ...Acl, ...obj };
}
});
if (!resExt) throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
const _resExt = JSON.parse(JSON.stringify(resExt));
// Build Parse /batch requests from your existing mapping (same as your current code)
const requests = Documents.map(x => {
const Signers = x.Signers;
const placeholders = x?.Placeholders?.filter(p => p?.Role !== 'prefill');
const allSigner = placeholders
?.map(
item => Signers?.find(e => item?.signerPtr?.objectId === e?.objectId) || item?.signerPtr
)
.filter(signer => signer && Object.keys(signer).length > 0);
const date = new Date();
const isoDate = date.toISOString();
let Acl = { [x.CreatedBy.objectId]: { read: true, write: true } };
if (allSigner && allSigner.length > 0) {
allSigner.forEach(x => {
if (x?.CreatedBy?.objectId) {
Acl = { ...Acl, [x.CreatedBy.objectId]: { read: true, write: true } };
}
let mailBody = x?.ExtUserPtr?.TenantId?.RequestBody || '';
let mailSubject = x?.ExtUserPtr?.TenantId?.RequestSubject || '';
return {
method: 'POST',
path: '/app/classes/contracts_Document',
body: {
Name: x.Name,
URL: x.URL,
Note: x.Note,
Description: x.Description,
CreatedBy: x.CreatedBy,
SendinOrder: x.SendinOrder || true,
ExtUserPtr: {
__type: 'Pointer',
className: x.ExtUserPtr.className,
objectId: x.ExtUserPtr?.objectId,
},
Placeholders: placeholders.map(y =>
y?.signerPtr?.objectId
? {
...y,
signerPtr: {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: y.signerPtr.objectId,
},
signerObjId: y.signerObjId,
email: y?.signerPtr?.Email || y?.email || '',
}
: { ...y, signerPtr: {}, signerObjId: '', email: y.email || '' }
),
SignedUrl: x.URL || x.SignedUrl,
SentToOthers: true,
Signers: allSigner?.map(y => ({
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: y.objectId,
})),
ACL: Acl,
SentToOthers: true,
RemindOnceInEvery: x.RemindOnceInEvery ? parseInt(x.RemindOnceInEvery) : 5,
AutomaticReminders: x.AutomaticReminders || false,
TimeToCompleteDays: x.TimeToCompleteDays ? parseInt(x.TimeToCompleteDays) : 15,
OriginIp: Ip,
DocSentAt: { __type: 'Date', iso: isoDate },
IsEnableOTP: x?.IsEnableOTP || false,
IsTourEnabled: x?.IsTourEnabled || false,
AllowModifications: x?.AllowModifications || false,
...(x?.SignatureType ? { SignatureType: x?.SignatureType } : {}),
...(x?.NotifyOnSignatures ? { NotifyOnSignatures: x?.NotifyOnSignatures } : {}),
...(x?.Bcc?.length > 0 ? { Bcc: x?.Bcc } : {}),
...(x?.RedirectUrl ? { RedirectUrl: x?.RedirectUrl } : {}),
...(mailBody ? { RequestBody: mailBody } : {}),
...(mailSubject ? { RequestSubject: mailSubject } : {}),
...(x?.objectId
? {
TemplateId: {
__type: 'Pointer',
className: 'contracts_Template',
objectId: x?.objectId,
},
}
: {}),
...(x?.PenColors?.length > 0 ? { PenColors: x?.PenColors } : {}),
},
};
});
if (requests?.length > 0) {
const newrequests = [requests?.[0]];
const response = await axios.post('batch', { requests: newrequests }, parseConfig);
// Handle the batch query response
// console.log('Batch query response:', response.data);
if (response.data && response.data.length > 0) {
const document = Documents?.[0];
const updateDocuments = {
...document,
objectId: response.data[0]?.success?.objectId,
createdAt: response.data[0]?.success?.createdAt,
};
deductcount(response.data.length, resExt.id);
sendMail(updateDocuments, publicUrl); //sessionToken
return 'success';
}
}
} catch (error) {
const code = error?.response?.data?.code || error?.response?.status || error?.code || 400;
const msg =
error?.response?.data?.error ||
error?.response?.data ||
error?.message ||
'Something went wrong.';
console.log('Error performing batch query:', code, msg);
throw new Parse.Error(code, msg);
}
} else {
throw new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'User not found.');
let mailBody = x?.ExtUserPtr?.TenantId?.RequestBody || '';
let mailSubject = x?.ExtUserPtr?.TenantId?.RequestSubject || '';
return {
method: 'POST',
path: '/app/classes/contracts_Document',
body: {
Name: x.Name,
URL: x.URL,
Note: x.Note,
Description: x.Description,
CreatedBy: x.CreatedBy,
SendinOrder: x.SendinOrder || true,
ExtUserPtr: {
__type: 'Pointer',
className: x.ExtUserPtr.className,
objectId: x.ExtUserPtr?.objectId,
},
Placeholders: placeholders.map(y =>
y?.signerPtr?.objectId
? {
...y,
signerPtr: {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: y.signerPtr.objectId,
},
signerObjId: y.signerObjId,
email: y?.signerPtr?.Email || y?.email || '',
}
: { ...y, signerPtr: {}, signerObjId: '', email: y.email || '' }
),
SignedUrl: x.URL || x.SignedUrl,
SentToOthers: true,
Signers: allSigner?.map(y => ({
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: y.objectId,
})),
ACL: Acl,
SentToOthers: true,
RemindOnceInEvery: x.RemindOnceInEvery ? parseInt(x.RemindOnceInEvery) : 5,
AutomaticReminders: x.AutomaticReminders || false,
TimeToCompleteDays: x.TimeToCompleteDays ? parseInt(x.TimeToCompleteDays) : 15,
OriginIp: Ip,
DocSentAt: { __type: 'Date', iso: isoDate },
IsEnableOTP: x?.IsEnableOTP || false,
IsTourEnabled: x?.IsTourEnabled || false,
AllowModifications: x?.AllowModifications || false,
...(x?.SignatureType ? { SignatureType: x?.SignatureType } : {}),
...(x?.NotifyOnSignatures ? { NotifyOnSignatures: x?.NotifyOnSignatures } : {}),
...(x?.Bcc?.length > 0 ? { Bcc: x?.Bcc } : {}),
...(x?.RedirectUrl ? { RedirectUrl: x?.RedirectUrl } : {}),
...(mailBody ? { RequestBody: mailBody } : {}),
...(mailSubject ? { RequestSubject: mailSubject } : {}),
...(x?.objectId
? {
TemplateId: {
__type: 'Pointer',
className: 'contracts_Template',
objectId: x?.objectId,
},
}
: {}),
...(x?.PenColors?.length > 0 ? { PenColors: x?.PenColors } : {}),
},
};
});
if (requests?.length > 0) {
const newrequests = [requests?.[0]];
const response = await axios.post('batch', { requests: newrequests }, parseConfig);
// Handle the batch query response
// console.log('Batch query response:', response.data);
if (response.data && response.data.length > 0) {
const document = Documents?.[0];
const updateDocuments = {
...document,
objectId: response.data[0]?.success?.objectId,
createdAt: response.data[0]?.success?.createdAt,
};
deductcount(response.data.length, resExt.id);
sendMail(updateDocuments, publicUrl); //sessionToken
return { total: 1, created: 1, failed: 0 };
}
}
}
export default async function createBatchDocs(request) {
const strDocuments = request.params.Documents;
const sessionToken = request.headers?.sessiontoken;
@@ -242,13 +318,20 @@ export default async function createBatchDocs(request) {
},
};
try {
let userId = '';
if (request?.user) {
return await batchQuery(request.user.id, Documents, Ip, parseConfig, type, publicUrl);
} else {
userId = request.user.id;
// return await batchQuery(request.user.id, Documents, Ip, parseConfig, type, publicUrl);
}
if (!userId) {
throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.');
}
// quicksend
return await startBulkSendInBackground(userId, Documents, Ip, parseConfig, type, publicUrl);
} catch (err) {
console.log('err in createbatchdoc', err);
console.log('createbatchdoc error: ', err);
const code = err?.code || 400;
const msg = err?.message || 'Something went wrong.';
throw new Parse.Error(code, msg);
@@ -14,7 +14,7 @@ async function sendDeclineMail(doc, publicUrl, userId, reason) {
const TenantAppName = appName;
const logo =
"<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>";
const opurl = ` <a href='mailto:complaint@opensiglabs.com' target=_blank>here</a>`;
const removePrefill =
doc?.Placeholders?.length > 0 && doc?.Placeholders?.filter(x => x?.Role !== 'prefill');
const signUser =
@@ -36,7 +36,7 @@ async function sendDeclineMail(doc, publicUrl, userId, reason) {
`<p>${pdfName} has been declined by ${signerName} "${signerEmail}" on ${new Date().toLocaleDateString()}.</p>` +
`<p>Decline Reason: ${reason || 'Not specified'}</p>` +
`<p><a href=${viewDocUrl} target=_blank>View Document</a></p></div></div><div><p>This is an automated email from ${TenantAppName}. For any queries regarding this email, ` +
`please contact the sender ${creatorEmail} directly. If you think this email is inappropriate or spam, you may file a complaints with ${TenantAppName}${opurl}.</p></div></div></body></html>`;
`please contact the sender ${creatorEmail} directly.</p></div></div></body></html>`;
const params = {
extUserId: sender.objectId,
@@ -1,39 +1,63 @@
import AWS from 'aws-sdk';
import { S3Client, GetObjectCommand } from '@aws-sdk/client-s3';
import { getSignedUrl as presign } from '@aws-sdk/s3-request-presigner';
import { useLocal } from '../../Utils.js';
import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';
import { isAuthenticated } from '../../utils/AuthUtils.js';
dotenv.config({ quiet: true });
export default function getPresignedUrl(url) {
function extractKeyFromUrl(url) {
// Create a new URL object
const parsedUrl = new URL(url);
// Get the pathname of the URL
const pathname = parsedUrl.pathname; // e.g. /mybucket/path/to/file.pdf (depends on baseUrl style)
// Extract the filename from the pathname
const filename = pathname.substring(pathname.lastIndexOf('/') + 1);
return filename;
}
function makeEndpoint(endpoint) {
if (!endpoint) return '';
if (endpoint.startsWith('http://') || endpoint.startsWith('https://')) {
return endpoint;
}
return `https://${endpoint}`;
}
function makeS3Client() {
const accessKeyId = process.env.DO_ACCESS_KEY_ID;
const secretAccessKey = process.env.DO_SECRET_ACCESS_KEY;
const region = process.env.DO_REGION;
const endpoint = makeEndpoint(process.env.DO_ENDPOINT);
return new S3Client({
region,
endpoint, // endpoint should be Url e.g. https://blr1.digitaloceanspaces.com)
credentials: { accessKeyId, secretAccessKey },
});
}
export default async function getPresignedUrl(url) {
if (url?.includes('files')) {
return presignedlocalUrl(url);
} else {
const credentials = {
accessKeyId: process.env.DO_ACCESS_KEY_ID,
secretAccessKey: process.env.DO_SECRET_ACCESS_KEY,
};
AWS.config.update({
credentials: credentials,
region: process.env.DO_REGION,
});
const spacesEndpoint = new AWS.Endpoint(process.env.DO_ENDPOINT);
const client = makeS3Client();
const s3 = new AWS.S3({ endpoint: spacesEndpoint, signatureVersion: 'v4' });
const bucket = process.env.DO_SPACE;
// Create a new URL object
const parsedUrl = new URL(url);
// Get the pathname of the URL
const pathname = parsedUrl.pathname;
// Extract the filename from the pathname
const filename = pathname.substring(pathname.lastIndexOf('/') + 1);
const key = extractKeyFromUrl(url);
const command = new GetObjectCommand({ Bucket: bucket, Key: key });
// Expires: 160 seconds
const expiresIn = 160;
// presignedGETURL return presignedUrl with expires time
const presignedGETURL = s3.getSignedUrl('getObject', {
Bucket: process.env.DO_SPACE,
Key: filename, //filename
Expires: 160, //time to expire in seconds
});
const presignedGETURL = await presign(client, command, { expiresIn });
return presignedGETURL;
}
}
@@ -68,7 +92,7 @@ export async function getSignedUrl(request) {
}
}
const presignedUrl = getPresignedUrl(url);
const presignedUrl = await getPresignedUrl(url);
return presignedUrl;
} else {
return url;
@@ -85,7 +109,7 @@ export async function getSignedUrl(request) {
if (url?.includes('files')) {
return presignedlocalUrl(url);
} else if (useLocal !== 'true') {
const presignedUrl = getPresignedUrl(url);
const presignedUrl = await getPresignedUrl(url);
return presignedUrl;
} else {
return url;
@@ -122,7 +122,7 @@ async function sendNotifyMail(doc, signUser, mailProvider, publicUrl) {
const TenantAppName = appName;
const logo =
"<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>";
const opurl = ` <a href='mailto:complaint@opensiglabs.com' target=_blank>here</a>`;
const auditTrailCount = doc?.AuditTrail?.filter(x => x.Activity === 'Signed')?.length || 0;
const removePrefill =
doc?.Placeholders?.length > 0 && doc?.Placeholders?.filter(x => x?.Role !== 'prefill');
@@ -142,7 +142,7 @@ async function sendNotifyMail(doc, signUser, mailProvider, publicUrl) {
`<div>${logo}</div><div style='padding:2px;font-family:system-ui;background-color:#47a3ad'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document signed by ${signerName}</p>` +
`</div><div style='padding:20px;font-family:system-ui;font-size:14px'><p>Dear ${creatorName},</p><p>${pdfName} has been signed by ${signerName} "${signerEmail}" successfully</p>` +
`<p><a href=${viewDocUrl} target=_blank>View Document</a></p></div></div><div><p>This is an automated email from ${TenantAppName}. For any queries regarding this email, ` +
`please contact the sender ${creatorEmail} directly. If you think this email is inappropriate or spam, you may file a complaints with ${TenantAppName}${opurl}.</p></div></div></body></html>`;
`please contact the sender ${creatorEmail} directly.</p></div></div></body></html>`;
const params = {
extUserId: sender.objectId,
@@ -169,7 +169,7 @@ async function sendCompletedMail(obj) {
const TenantAppName = appName;
const logo =
"<img src='https://qikinnovation.ams3.digitaloceanspaces.com/logo.png' height='50' style='padding:20px'/>";
const opurl = ` <a href='mailto:complaint@opensiglabs.com' target=_blank>here</a>`;
let signersMail;
if (doc?.Signers?.length > 0) {
const isOwnerExistsinSigners = doc?.Signers?.find(x => x.Email === sender.Email);
@@ -185,8 +185,7 @@ async function sendCompletedMail(obj) {
"<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /></head><body><div style='background-color:#f5f5f5;padding:20px'><div style='background-color:white'>" +
`<div>${logo}</div><div style='padding:2px;font-family:system-ui;background-color:#47a3ad'><p style='font-size:20px;font-weight:400;color:white;padding-left:20px'>Document signed successfully</p></div><div>` +
`<p style='padding:20px;font-family:system-ui;font-size:14px'>All parties have successfully signed the document <b>"${pdfName}"</b>. Kindly download the document from the attachment.</p>` +
`</div></div><div><p>This is an automated email from ${TenantAppName}. For any queries regarding this email, please contact the sender ${sender.Email} directly.` +
`If you think this email is inappropriate or spam, you may file a complaints with ${TenantAppName}${opurl}.</p></div></div></body></html>`;
`</div></div><div><p>This is an automated email from ${TenantAppName}. For any queries regarding this email, please contact the sender ${sender.Email} directly.</p></div></div></body></html>`;
if (obj?.isCustomMail) {
const tenant = sender?.TenantId;
@@ -224,6 +223,7 @@ async function sendCompletedMail(obj) {
const variables = {
document_title: pdfName,
note: doc?.Note,
sender_name: sender.Name,
sender_mail: doc?.SenderMail || sender.Email,
sender_phone: sender?.Phone || '',
@@ -10,6 +10,8 @@ export default function reportJson(id, currentUserId) {
'ExtUserPtr.Name',
'ExtUserPtr.Email',
'ExtUserPtr.DownloadFilenameFormat',
'ExtUserPtr.Company',
'ExtUserPtr.Phone',
'Signers.Name',
'Signers.Email',
'Signers.Phone',
@@ -2,10 +2,14 @@ import fs from 'node:fs';
import https from 'https';
import formData from 'form-data';
import Mailgun from 'mailgun.js';
import { smtpenable, smtpsecure, updateMailCount } from '../../Utils.js';
import { appName, smtpenable, smtpsecure, updateMailCount } from '../../Utils.js';
import { createTransport } from 'nodemailer';
import axios from 'axios';
async function sendMailProvider(req, plan, monthchange) {
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;
try {
let transporterSMTP;
@@ -125,7 +129,7 @@ async function sendMailProvider(req, plan, monthchange) {
to: req.params.recipient,
subject: req.params.subject,
text: req.params.text || 'mail',
html: req.params.html || '',
html: req.params?.html ? req.params.html + reportMsg : '',
attachments: smtpenable ? attachment : undefined,
attachment: smtpenable ? undefined : attachment,
bcc: req.params.bcc ? req.params.bcc : undefined,
@@ -135,8 +139,8 @@ async function sendMailProvider(req, plan, monthchange) {
const res = await transporterSMTP.sendMail(messageParams);
console.log('smtp transporter res: ', res?.response);
if (!res.err) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId, plan, monthchange);
if (extUserId) {
await updateMailCount(extUserId);
}
if (fs.existsSync(certificatePath)) {
try {
@@ -159,8 +163,8 @@ async function sendMailProvider(req, plan, monthchange) {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('mailgun res: ', res?.status);
if (res.status === 200) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId, plan, monthchange);
if (extUserId) {
await updateMailCount(extUserId);
}
if (fs.existsSync(certificatePath)) {
try {
@@ -219,7 +223,7 @@ async function sendMailProvider(req, plan, monthchange) {
to: req.params.recipient,
subject: req.params.subject,
text: req.params.text || 'mail',
html: req.params.html || '',
html: req.params?.html ? req.params.html + reportMsg : '',
bcc: req.params.bcc ? req.params.bcc : undefined,
replyTo: replyto ? replyto : undefined,
};
@@ -228,8 +232,8 @@ async function sendMailProvider(req, plan, monthchange) {
const res = await transporterSMTP.sendMail(messageParams);
console.log('smtp transporter res: ', res?.response);
if (!res.err) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId, plan, monthchange);
if (extUserId) {
await updateMailCount(extUserId);
}
return { status: 'success' };
}
@@ -238,8 +242,8 @@ async function sendMailProvider(req, plan, monthchange) {
const res = await mailgunClient.messages.create(mailgunDomain, messageParams);
console.log('mailgun res: ', res?.status);
if (res.status === 200) {
if (req.params?.extUserId) {
await updateMailCount(req.params.extUserId, plan, monthchange);
if (extUserId) {
await updateMailCount(extUserId);
}
return { status: 'success' };
}
@@ -16,8 +16,8 @@ export default async function triggerEvent(request) {
const docRes = await docQuery.get(docId, { useMasterKey: true });
const _docRes = docRes && docRes?.toJSON();
const isEnableOTP = docRes?.get('IsEnableOTP') || false;
const ipAddress = request.headers['x-real-ip'] || '';
let userId;
if (isEnableOTP) {
let userId;
if (sessiontoken) {
@@ -36,29 +36,44 @@ export default async function triggerEvent(request) {
if (event === 'viewed' && contactId) {
const auditTrail = Array.isArray(_docRes.AuditTrail) ? _docRes.AuditTrail : [];
const isUserExist = auditTrail.some(x => x?.UserPtr?.objectId === contactId && x?.ViewedOn);
if (!isUserExist) {
const contactPtr = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: contactId,
};
const contactPtr = {
__type: 'Pointer',
className: 'contracts_Contactbook',
objectId: contactId,
};
const date = new Date().toISOString();
const newEntry = {
UserPtr: contactPtr,
SignedUrl: _docRes?.SignedUrl || '',
Activity: 'Viewed',
ipAddress,
ViewedOn: date,
};
const date = new Date().toISOString();
const newEntry = {
UserPtr: contactPtr,
SignedUrl: _docRes?.SignedUrl || '',
const existingIndex = auditTrail.findIndex(x => x?.UserPtr?.objectId === contactId);
let updatedAuditTrail;
if (existingIndex !== -1) {
// update existing entry
updatedAuditTrail = [...auditTrail];
updatedAuditTrail[existingIndex] = {
...updatedAuditTrail[existingIndex],
SignedUrl: _docRes?.SignedUrl || updatedAuditTrail[existingIndex]?.SignedUrl || '',
Activity: 'Viewed',
ipAddress: request.headers['x-real-ip'],
ipAddress,
ViewedOn: date,
};
// update Audit trail entry
const updateDoc = new Parse.Object('contracts_Document');
updateDoc.id = docRes.id;
updateDoc.set('AuditTrail', [...auditTrail, newEntry]);
await updateDoc.save(null, { useMasterKey: true });
} else {
// add new entry
updatedAuditTrail = [...auditTrail, newEntry];
}
// save only once
const updateDoc = new Parse.Object('contracts_Document');
updateDoc.id = docRes.id;
updateDoc.set('AuditTrail', updatedAuditTrail);
await updateDoc.save(null, { useMasterKey: true });
}
return { message: 'event called!' };