From 882db6acc9c3de6358d81aed9556594d1d42d26f Mon Sep 17 00:00:00 2001 From: prafull-opensignlabs Date: Mon, 19 Aug 2024 11:28:22 +0530 Subject: [PATCH] fix: validation of generate token --- .../public/locales/en/translation.json | 6 +- .../public/locales/fr/translation.json | 6 +- apps/OpenSign/src/pages/GenerateToken.js | 146 ++++- .../v1/routes/CreateDocumentWithTemplate.js | 556 ++++++++-------- .../v1/routes/createDocumentwithCoordinate.js | 594 +++++++++--------- apps/OpenSignServer/cloud/main.js | 4 + .../cloud/parsefunction/AllowedApis.js | 35 ++ .../cloud/parsefunction/BuyApis.js | 87 +++ .../cloud/parsefunction/getapitoken.js | 21 +- 9 files changed, 867 insertions(+), 588 deletions(-) create mode 100644 apps/OpenSignServer/cloud/parsefunction/AllowedApis.js create mode 100644 apps/OpenSignServer/cloud/parsefunction/BuyApis.js diff --git a/apps/OpenSign/public/locales/en/translation.json b/apps/OpenSign/public/locales/en/translation.json index 9d9a53375..67fac42b1 100644 --- a/apps/OpenSign/public/locales/en/translation.json +++ b/apps/OpenSign/public/locales/en/translation.json @@ -602,6 +602,10 @@ "Proceed":"Proceed", "Select":"Select", "Add-seats":"Add Seats", - "user-name-exist":"user name already exist" + "user-name-exist":"user name already exist", + "buyapiaddon":"Buy APIs", + "quantityofapis" :"Quantity of APIs", + "additionalapis":"Please purchase add-on APIs", + "remainingapis":"Remaining APIs:" } \ No newline at end of file diff --git a/apps/OpenSign/public/locales/fr/translation.json b/apps/OpenSign/public/locales/fr/translation.json index fa0df737a..c36e0382d 100644 --- a/apps/OpenSign/public/locales/fr/translation.json +++ b/apps/OpenSign/public/locales/fr/translation.json @@ -602,7 +602,11 @@ "Total-price-for-next-time":"Prix ​​total pour la prochaine fois", "Proceed":"Procéder", "Select":"sélectionner", - "Add-seats":"Ajouter des sièges" + "Add-seats":"Ajouter des sièges", + "buyapiaddon":"Acheter des API", + "quantityofapis":"Quantité d'API", + "additionalapis":"Veuillez acheter des API complémentaires", + "remainingapis":"API restantes :" } \ No newline at end of file diff --git a/apps/OpenSign/src/pages/GenerateToken.js b/apps/OpenSign/src/pages/GenerateToken.js index 1d2b73312..5504965e2 100644 --- a/apps/OpenSign/src/pages/GenerateToken.js +++ b/apps/OpenSign/src/pages/GenerateToken.js @@ -11,6 +11,7 @@ import SubscribeCard from "../primitives/SubscribeCard"; import Tour from "reactour"; import { validplan } from "../json/plansArr"; import { useTranslation } from "react-i18next"; +import Parse from "parse"; function GenerateToken() { const { t } = useTranslation(); @@ -18,10 +19,20 @@ function GenerateToken() { const [parseAppId] = useState(localStorage.getItem("parseAppId")); const [apiToken, SetApiToken] = useState(""); const [isLoader, setIsLoader] = useState(true); - const [isModal, setIsModal] = useState(false); + const [isModal, setIsModal] = useState({ + generateapi: false, + buyapis: false + }); const [isSubscribe, setIsSubscribe] = useState({ plan: "", isValid: false }); const [isAlert, setIsAlert] = useState({ type: "success", msg: "" }); const [isTour, setIsTour] = useState(false); + const [amount, setAmount] = useState({ + quantity: 1, + priceperapi: 0.15, + totalapis: 0, + price: 0.15 + }); + const [isFormLoader, setIsFormLoader] = useState(false); useEffect(() => { fetchToken(); // eslint-disable-next-line @@ -38,15 +49,16 @@ function GenerateToken() { const subscribe = await checkIsSubscribed(); setIsSubscribe(subscribe); } - const url = parseBaseUrl + "functions/getapitoken"; - const headers = { - "Content-Type": "application/json", - "X-Parse-Application-Id": parseAppId, - sessiontoken: localStorage.getItem("accesstoken") - }; - const res = await axios.post(url, {}, { headers: headers }); + const extUser = + localStorage.getItem("Extand_Class") && + JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; + const res = await Parse.Cloud.run("getapitoken"); if (res) { - SetApiToken(res.data?.result?.result); + const allowedapis = await Parse.Cloud.run("allowedapis", { + tenantId: extUser?.TenantId?.objectId + }); + setAmount((obj) => ({ ...obj, totalapis: allowedapis })); + SetApiToken(res?.result); } setIsLoader(false); } catch (err) { @@ -61,7 +73,7 @@ function GenerateToken() { setIsTour(true); } else { setIsLoader(true); - setIsModal(false); + setIsModal((obj) => ({ ...obj, generateapi: false })); try { const url = parseBaseUrl + "functions/generateapitoken"; const headers = { @@ -92,12 +104,51 @@ function GenerateToken() { const copytoclipboard = (text) => { copytoData(text); setIsAlert({ type: "success", msg: t("copied") }); - setTimeout(() => { - setIsAlert({ type: "success", msg: "" }); - }, 1500); // Reset copied state after 1.5 seconds + setTimeout(() => setIsAlert({ type: "success", msg: "" }), 1500); // Reset copied state after 1.5 seconds + }; + const handleModal = () => { + setIsModal((obj) => ({ ...obj, generateapi: !obj.generateapi })); }; - const handleModal = () => setIsModal(!isModal); + const handleBuyAPIsModal = () => { + setIsModal((obj) => ({ ...obj, buyapis: !obj.buyapis })); + }; + + const handlePricePerAPIs = (e) => { + const quantity = e.target?.value; + const price = + quantity > 0 + ? (Math.round(quantity * amount.priceperapi * 100) / 100).toFixed(2) + : 1 * amount.priceperapi; + setAmount((prev) => ({ ...prev, quantity: quantity, price: price })); + }; + const handleAddOnApiSubmit = async (e) => { + e.preventDefault(); + e.stopPropagation(); + setIsFormLoader(true); + const extUser = + localStorage.getItem("Extand_Class") && + JSON.parse(localStorage.getItem("Extand_Class"))?.[0]; + try { + const resAddon = await Parse.Cloud.run("buyapis", { + apis: amount.quantity, + tenantId: extUser?.TenantId?.objectId + }); + if (resAddon) { + const _resAddon = JSON.parse(JSON.stringify(resAddon)); + if (_resAddon.status === "success") { + // setAllowedUser(amount.quantity); + setAmount((obj) => ({ ...obj, totalapis: _resAddon.addon })); + } + } + } catch (err) { + console.log("Err in buy addon", err); + setIsAlert({ type: "danger", msg: t("something-went-wrong-mssg") }); + } finally { + setTimeout(() => setIsAlert({ type: "success", msg: "" }), 2000); + setIsFormLoader(false); + } + }; return ( @@ -112,9 +163,7 @@ function GenerateToken() { <h1 className={"ml-4 mt-3 mb-2 font-semibold"}> OpenSign™ {t("API")}{" "} <Tooltip - url={ - "https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1" - } + url="https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1" isSubscribe={true} /> </h1> @@ -151,8 +200,12 @@ function GenerateToken() { {apiToken ? t("regenerate-token") : t("generate-token")} </button> </li> + <div className="text-xs md:text-[15px] my-4"> + {t("remainingapis")} {amount.totalapis}{" "} + </div> + <hr /> </ul> - <div className="flex items-center justify-center"> + <div className="flex flex-col md:flex-row items-center justify-center gap-0 md:gap-1"> <button type="button" onClick={() => @@ -160,13 +213,20 @@ function GenerateToken() { "https://docs.opensignlabs.com/docs/API-docs/opensign-api-v-1" ) } - className="op-btn op-btn-secondary mt-2 mb-3 px-8" + className="op-btn op-btn-secondary mt-2 md:mb-3 px-8" > {t("view-docs")} </button> + <button + type="button" + onClick={() => handleBuyAPIsModal()} + className="op-btn op-btn-secondary mt-2 mb-3 px-8" + > + {t("buyapiaddon")} + </button> </div> <ModalUi - isOpen={isModal} + isOpen={isModal.generateapi} title={apiToken ? t("regenerate-token") : t("generate-token")} handleClose={handleModal} > @@ -191,6 +251,52 @@ function GenerateToken() { </div> </div> </ModalUi> + {/* buy apis */} + <ModalUi + isOpen={isModal.buyapis} + title={"Addon apis"} + handleClose={handleBuyAPIsModal} + > + {isFormLoader && ( + <div className="absolute w-full h-full inset-0 flex justify-center items-center bg-base-content/30 z-50"> + <Loader /> + </div> + )} + <form onSubmit={handleAddOnApiSubmit} className="p-3"> + <p className="flex justify-center text-center mx-2 mb-3 text-base op-text-accent font-medium"> + {t("additionalapis")} + </p> + <div className="mb-3 flex justify-between"> + <label + htmlFor="quantity" + className="block text-xs text-gray-700 font-semibold" + > + {t("quantityofapis")} + <span className="text-[red] text-[13px]"> *</span> + </label> + <input + type="number" + name="quantity" + value={amount.quantity} + onChange={(e) => handlePricePerAPIs(e)} + className="w-1/4 op-input op-input-bordered op-input-sm focus:outline-none hover:border-base-content text-xs" + required + /> + </div> + <div className="mb-3 flex justify-between"> + <label className="block text-xs text-gray-700 font-semibold"> + {t("Price")} (1 * {amount.priceperapi}) + </label> + <div className="w-1/4 flex justify-center items-center text-sm"> + USD {amount.price} + </div> + </div> + <hr className="text-base-content mb-3" /> + <button className="op-btn op-btn-primary w-full mt-2"> + {t("Proceed")} + </button> + </form> + </ModalUi> </div> {!validplan[isSubscribe.plan] && isEnableSubscription && ( <div data-tut="apisubscribe"> diff --git a/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js b/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js index 513213aa4..e9e167036 100644 --- a/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js +++ b/apps/OpenSignServer/cloud/customRoute/v1/routes/CreateDocumentWithTemplate.js @@ -4,14 +4,9 @@ import { cloudServerUrl, customAPIurl, replaceMailVaribles } from '../../../../U // `sendDoctoWebhook` is used to send res data of document on webhook async function sendDoctoWebhook(doc, WebhookUrl, userId) { if (WebhookUrl) { - const params = { - event: 'created', - ...doc, - }; + const params = { event: 'created', ...doc }; await axios - .post(WebhookUrl, params, { - headers: { 'Content-Type': 'application/json' }, - }) + .post(WebhookUrl, params, { headers: { 'Content-Type': 'application/json' } }) .then(res => { try { // console.log('res ', res); @@ -62,6 +57,7 @@ export default async function createDocumentWithTemplate(request, response) { if (!reqToken) { return response.status(400).json({ error: 'Please Provide API Token' }); } + const tokenQuery = new Parse.Query('appToken'); tokenQuery.equalTo('token', reqToken); tokenQuery.include('userId'); @@ -74,275 +70,306 @@ export default async function createDocumentWithTemplate(request, response) { className: '_User', objectId: parseUser.userId.objectId, }; - const templateQuery = new Parse.Query('contracts_Template'); - templateQuery.include('ExtUserPtr'); - const templateRes = await templateQuery.get(templateId, { useMasterKey: true }); - if (templateRes) { - const template = JSON.parse(JSON.stringify(templateRes)); - if (template?.Placeholders?.length > 0) { - const emptyplaceholder = template?.Placeholders.filter(x => !x.signerObjId); - const isValid = - signers.length >= emptyplaceholder.length && - signers.length <= template?.Placeholders?.length; - const placeholder = - signers.length > emptyplaceholder.length ? template.Placeholders : emptyplaceholder; - const updateSigners = placeholder.every(y => signers?.some(x => x.role === y.Role)); - // console.log('isValid ', isValid); - if (isValid && updateSigners) { - //Check if every item's placeholders contain at least one placeholder with type 'signature'. - let isSignature = template?.Placeholders?.every(item => - item?.placeHolder.some(x => x?.pos.some(data => data?.type === 'signature')) - ); - if (!isSignature) { - return response - .status(400) - .json({ error: 'Please add at least one signature widget for all signers' }); - } - const folderPtr = { - __type: 'Pointer', - className: 'contracts_Document', - objectId: folderId, - }; - const object = new Parse.Object('contracts_Document'); - object.set('Name', template.Name); - if (template?.Note) { - object.set('Note', template.Note); - } - if (template?.Description) { - object.set('Description', template.Description); - } - object.set('IsSendMail', send_email); - if (sendInOrder) { - object.set('SendinOrder', sendInOrder); - } else if (template?.SendinOrder && template?.SendinOrder) { - object.set('SendinOrder', template?.SendinOrder); - } - let templateSigner = template?.Signers ? template?.Signers : []; - let contact = []; - if (signers && signers.length > 0) { - let parseSigners = [...signers]; - let createContactUrl = protocol + '/v1/createcontact'; + const extUsers = new Parse.Query('contracts_Users'); + extUsers.equalTo('UserId', userPtr); + const extUser = await extUsers.first({ useMasterKey: true }); - for (const obj of parseSigners) { - const body = { - name: obj?.name || '', - email: obj?.email || '', - phone: obj?.phone || '', - }; - try { - const res = await axios.post(createContactUrl, body, { - headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken }, - }); - const contactPtr = { - __type: 'Pointer', - className: 'contracts_Contactbook', - objectId: res.data?.objectId, + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: extUser.get('TenantId').id, + }); + subscription.include('ExtUserPtr'); + subscription.greaterThan('AllowedApis', 0); + const resSub = await subscription.first({ useMasterKey: true }); + + if (resSub) { + const templateQuery = new Parse.Query('contracts_Template'); + templateQuery.include('ExtUserPtr'); + const templateRes = await templateQuery.get(templateId, { useMasterKey: true }); + if (templateRes) { + const template = JSON.parse(JSON.stringify(templateRes)); + if (template?.Placeholders?.length > 0) { + const emptyplaceholder = template?.Placeholders.filter(x => !x.signerObjId); + const isValid = + signers.length >= emptyplaceholder.length && + signers.length <= template?.Placeholders?.length; + const placeholder = + signers.length > emptyplaceholder.length ? template.Placeholders : emptyplaceholder; + const updateSigners = placeholder.every(y => signers?.some(x => x.role === y.Role)); + // console.log('isValid ', isValid); + if (isValid && updateSigners) { + //Check if every item's placeholders contain at least one placeholder with type 'signature'. + let isSignature = template?.Placeholders?.every(item => + item?.placeHolder.some(x => x?.pos.some(data => data?.type === 'signature')) + ); + if (!isSignature) { + return response + .status(400) + .json({ error: 'Please add at least one signature widget for all signers' }); + } + const folderPtr = { + __type: 'Pointer', + className: 'contracts_Document', + objectId: folderId, + }; + const object = new Parse.Object('contracts_Document'); + object.set('Name', template.Name); + if (template?.Note) { + object.set('Note', template.Note); + } + if (template?.Description) { + object.set('Description', template.Description); + } + object.set('IsSendMail', send_email); + if (sendInOrder) { + object.set('SendinOrder', sendInOrder); + } else if (template?.SendinOrder && template?.SendinOrder) { + object.set('SendinOrder', template?.SendinOrder); + } + let templateSigner = template?.Signers ? template?.Signers : []; + let contact = []; + if (signers && signers.length > 0) { + let parseSigners = [...signers]; + let createContactUrl = protocol + '/v1/createcontact'; + + for (const obj of parseSigners) { + const body = { + name: obj?.name || '', + email: obj?.email || '', + phone: obj?.phone || '', }; - const newObj = { ...obj, contactPtr: contactPtr }; - contact.push(newObj); - } catch (err) { - // console.log('err ', err); - if (err?.response?.data?.objectId) { + try { + const res = await axios.post(createContactUrl, body, { + headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken }, + }); const contactPtr = { __type: 'Pointer', className: 'contracts_Contactbook', - objectId: err.response.data?.objectId, + objectId: res.data?.objectId, }; const newObj = { ...obj, contactPtr: contactPtr }; contact.push(newObj); - } else { - console.log('err ', err); + } catch (err) { + // console.log('err ', err); + if (err?.response?.data?.objectId) { + const contactPtr = { + __type: 'Pointer', + className: 'contracts_Contactbook', + objectId: err.response.data?.objectId, + }; + const newObj = { ...obj, contactPtr: contactPtr }; + contact.push(newObj); + } else { + console.log('err ', err); + } } } - } - const contactPtrs = contact.map(x => x.contactPtr); - object.set('Signers', [...templateSigner, ...contactPtrs]); + const contactPtrs = contact.map(x => x.contactPtr); + object.set('Signers', [...templateSigner, ...contactPtrs]); - let updatedPlaceholder = template?.Placeholders?.map(x => { - let matchingSigner = contact.find(y => x.Role && x.Role === y.role); + let updatedPlaceholder = template?.Placeholders?.map(x => { + let matchingSigner = contact.find(y => x.Role && x.Role === y.role); - if (matchingSigner) { - return { - ...x, - signerObjId: matchingSigner?.contactPtr?.objectId, - signerPtr: matchingSigner?.contactPtr, - }; - } else { - return { - ...x, - }; - } - }); - object.set('Placeholders', updatedPlaceholder); - } else { - object.set('Signers', templateSigner); - } - object.set('URL', template.URL); - object.set('SignedUrl', template.URL); - object.set('SentToOthers', true); - if (TimeToCompleteDays) { - object.set('TimeToCompleteDays', TimeToCompleteDays); - } - object.set('CreatedBy', template.CreatedBy); - object.set('ExtUserPtr', { - __type: 'Pointer', - className: 'contracts_Users', - objectId: template.ExtUserPtr.objectId, - }); - if (folderId) { - object.set('Folder', folderPtr); - } - const newACL = new Parse.ACL(); - newACL.setPublicReadAccess(false); - newACL.setPublicWriteAccess(false); - newACL.setReadAccess(userPtr.objectId, true); - newACL.setWriteAccess(userPtr.objectId, true); - object.setACL(newACL); - const res = await object.save(null, { useMasterKey: true }); - - const newDate = new Date(); - newDate.setDate(newDate.getDate() + 15); - const localExpireDate = newDate.toLocaleDateString('en-US', { - day: 'numeric', - month: 'long', - year: 'numeric', - }); - let sender = template.ExtUserPtr.Email; - let sendMail; - if (send_email === false) { - console.log("don't send mail"); - } else { - let contactMail = contact; - if (sendInOrder) { - contactMail = contact.slice(); - contactMail.splice(1); - } - for (let i = 0; i < contactMail.length; i++) { - try { - const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png'; - let url = `${cloudServerUrl}/functions/sendmailv3/`; - const headers = { - 'Content-Type': 'application/json', - 'X-Parse-Application-Id': process.env.APP_ID, - 'X-Parse-Master-Key': process.env.MASTER_KEY, - }; - - const objectId = contactMail[i].contactPtr.objectId; - const hostUrl = baseUrl.origin; - //encode this url value `${res.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function - const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`); - let signPdf = `${hostUrl}/login/${encodeBase64}`; - const openSignUrl = 'https://www.opensignlabs.com/contact-us'; - const orgName = template.ExtUserPtr.Company ? template.ExtUserPtr.Company : ''; - const themeBGcolor = '#47a3ad'; - const email_html = - "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src='" + - imgPng + - "' height='50' style='padding:20px; width:170px; height:40px;' /></div><div style='padding: 2px;font-family: system-ui;background-color:" + - themeBGcolor + - ";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " + - template.ExtUserPtr.Name + - ' has requested you to review and sign <strong> ' + - template.Name + - "</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" + - sender + - "</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " + - orgName + - "</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" + - localExpireDate + - "</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" + - signPdf + - "> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px;'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + - sender + - ' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' + - openSignUrl + - ' target=_blank>here</a>.</p> </div></div></body> </html>'; - - let replaceVar; - const variables = { - document_title: template.Name, - sender_name: template.ExtUserPtr.Name, - sender_mail: template.ExtUserPtr.Email, - sender_phone: template.ExtUserPtr?.Phone || '', - receiver_name: contactMail[i].name, - receiver_email: contactMail[i].email, - receiver_phone: contactMail[i]?.phone || '', - expiry_date: localExpireDate, - company_name: orgName, - signing_url: signPdf, - }; - if (email_subject && email_body) { - replaceVar = replaceMailVaribles(email_subject, email_body, variables); - } else if (email_subject) { - replaceVar = replaceMailVaribles(email_subject, '', variables); - replaceVar = { subject: replaceVar.subject, body: email_html }; - } else if (email_body) { - replaceVar = replaceMailVaribles( - `${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`, - email_body, - variables - ); - } else { - replaceVar = { - subject: `${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`, - body: email_html, + if (matchingSigner) { + return { + ...x, + signerObjId: matchingSigner?.contactPtr?.objectId, + signerPtr: matchingSigner?.contactPtr, }; + } else { + return { ...x }; } + }); + object.set('Placeholders', updatedPlaceholder); + } else { + object.set('Signers', templateSigner); + } + object.set('URL', template.URL); + object.set('SignedUrl', template.URL); + object.set('SentToOthers', true); + if (TimeToCompleteDays) { + object.set('TimeToCompleteDays', TimeToCompleteDays); + } + object.set('CreatedBy', template.CreatedBy); + object.set('ExtUserPtr', { + __type: 'Pointer', + className: 'contracts_Users', + objectId: template.ExtUserPtr.objectId, + }); + if (folderId) { + object.set('Folder', folderPtr); + } + const newACL = new Parse.ACL(); + newACL.setPublicReadAccess(false); + newACL.setPublicWriteAccess(false); + newACL.setReadAccess(userPtr.objectId, true); + newACL.setWriteAccess(userPtr.objectId, true); + object.setACL(newACL); + const res = await object.save(null, { useMasterKey: true }); - const subject = replaceVar.subject; - const html = replaceVar.body; - let params = { - recipient: contactMail[i].email, - subject: subject, - from: sender, - html: html, - extUserId: template.ExtUserPtr.objectId, - }; - sendMail = await axios.post(url, params, { headers: headers }); - } catch (error) { - console.log('error', error); + const newDate = new Date(); + newDate.setDate(newDate.getDate() + 15); + const localExpireDate = newDate.toLocaleDateString('en-US', { + day: 'numeric', + month: 'long', + year: 'numeric', + }); + let sender = template.ExtUserPtr.Email; + let sendMail; + if (send_email === false) { + console.log("don't send mail"); + } else { + let contactMail = contact; + if (sendInOrder) { + contactMail = contact.slice(); + contactMail.splice(1); + } + for (let i = 0; i < contactMail.length; i++) { + try { + const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png'; + let url = `${cloudServerUrl}/functions/sendmailv3/`; + const headers = { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': process.env.APP_ID, + 'X-Parse-Master-Key': process.env.MASTER_KEY, + }; + + const objectId = contactMail[i].contactPtr.objectId; + const hostUrl = baseUrl.origin; + //encode this url value `${res.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function + const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`); + let signPdf = `${hostUrl}/login/${encodeBase64}`; + const openSignUrl = 'https://www.opensignlabs.com/contact-us'; + const orgName = template.ExtUserPtr.Company ? template.ExtUserPtr.Company : ''; + const themeBGcolor = '#47a3ad'; + const email_html = + "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'> <div style='box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src='" + + imgPng + + "' height='50' style='padding:20px; width:170px; height:40px;' /></div><div style='padding: 2px;font-family: system-ui;background-color:" + + themeBGcolor + + ";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " + + template.ExtUserPtr.Name + + ' has requested you to review and sign <strong> ' + + template.Name + + "</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" + + sender + + "</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " + + orgName + + "</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" + + localExpireDate + + "</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" + + signPdf + + "> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px;'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + + sender + + ' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' + + openSignUrl + + ' target=_blank>here</a>.</p> </div></div></body> </html>'; + + let replaceVar; + const variables = { + document_title: template.Name, + sender_name: template.ExtUserPtr.Name, + sender_mail: template.ExtUserPtr.Email, + sender_phone: template.ExtUserPtr?.Phone || '', + receiver_name: contactMail[i].name, + receiver_email: contactMail[i].email, + receiver_phone: contactMail[i]?.phone || '', + expiry_date: localExpireDate, + company_name: orgName, + signing_url: signPdf, + }; + if (email_subject && email_body) { + replaceVar = replaceMailVaribles(email_subject, email_body, variables); + } else if (email_subject) { + replaceVar = replaceMailVaribles(email_subject, '', variables); + replaceVar = { subject: replaceVar.subject, body: email_html }; + } else if (email_body) { + replaceVar = replaceMailVaribles( + `${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`, + email_body, + variables + ); + } else { + replaceVar = { + subject: `${template.ExtUserPtr.Name} has requested you to sign "${template.Name}"`, + body: email_html, + }; + } + + const subject = replaceVar.subject; + const html = replaceVar.body; + let params = { + recipient: contactMail[i].email, + subject: subject, + from: sender, + html: html, + extUserId: template.ExtUserPtr.objectId, + }; + sendMail = await axios.post(url, params, { headers: headers }); + } catch (error) { + console.log('error', error); + } } } - } - // if (sendMail.data.result.status === 'success') { - try { - const doc = { - objectId: res.id, - file: template?.URL, - name: template?.Name, - note: template?.Note || '', - description: template?.Description || '', - signers: contact?.map(x => ({ - name: x.name, - email: x.email, - phone: x?.phone || '', - })), - createdAt: res.createdAt, - }; - if (template.ExtUserPtr && template.ExtUserPtr?.Webhook) { - sendDoctoWebhook(doc, template.ExtUserPtr?.Webhook, userPtr?.objectId); + // if (sendMail.data.result.status === 'success') { + try { + const doc = { + objectId: res.id, + file: template?.URL, + name: template?.Name, + note: template?.Note || '', + description: template?.Description || '', + signers: contact?.map(x => ({ + name: x.name, + email: x.email, + phone: x?.phone || '', + })), + createdAt: res.createdAt, + }; + if (template.ExtUserPtr && template.ExtUserPtr?.Webhook) { + sendDoctoWebhook(doc, template.ExtUserPtr?.Webhook, userPtr?.objectId); + } + } catch (err) { + console.log('Err', err); } - } catch (err) { - console.log('Err', err); - } - if (request.posthog) { - request.posthog?.capture({ - distinctId: parseUser.userId.email, - event: 'api_create_document_with_templateid', - properties: { response_code: 200 }, + if (request.posthog) { + request.posthog?.capture({ + distinctId: parseUser.userId.email, + event: 'api_create_document_with_templateid', + properties: { response_code: 200 }, + }); + } + const updateApiCount = + resSub?.get('AllowedApis') && resSub.get('AllowedApis') > 0 + ? resSub.get('AllowedApis') - 1 + : 0; + const subscriptionCls = new Parse.Object('contracts_Subscriptions'); + subscriptionCls.id = resSub.id; + subscriptionCls.set('AllowedApis', updateApiCount); + const resSubcription = await subscriptionCls.save(null, { useMasterKey: true }); + // console.log("resSubcription ", resSubcription) + return response.json({ + objectId: res.id, + signurl: contact.map(x => ({ + email: x.email, + url: `${baseUrl.origin}/login/${btoa( + `${res.id}/${x.email}/${x.contactPtr.objectId}` + )}`, + })), + message: 'Document sent successfully!', }); + } else { + if (request.posthog) { + request.posthog?.capture({ + distinctId: parseUser.userId.email, + event: 'api_create_document_with_templateid', + properties: { response_code: 400 }, + }); + } + return response.status(400).json({ error: 'Please provide signers properly!' }); } - return response.json({ - objectId: res.id, - signurl: contact.map(x => ({ - email: x.email, - url: `${baseUrl.origin}/login/${btoa( - `${res.id}/${x.email}/${x.contactPtr.objectId}` - )}`, - })), - message: 'Document sent successfully!', - }); - // } } else { if (request.posthog) { request.posthog?.capture({ @@ -351,27 +378,22 @@ export default async function createDocumentWithTemplate(request, response) { properties: { response_code: 400 }, }); } - return response.status(400).json({ error: 'Please provide signers properly!' }); + return response.status(400).json({ error: 'Please setup template properly!' }); } } else { if (request.posthog) { request.posthog?.capture({ distinctId: parseUser.userId.email, event: 'api_create_document_with_templateid', - properties: { response_code: 400 }, + properties: { response_code: 404 }, }); } - return response.status(400).json({ error: 'Please setup template properly!' }); + return response.status(404).json({ error: 'Invalid template id!' }); } } else { - if (request.posthog) { - request.posthog?.capture({ - distinctId: parseUser.userId.email, - event: 'api_create_document_with_templateid', - properties: { response_code: 404 }, - }); - } - return response.status(404).json({ error: 'Invalid template id!' }); + return response + .status(429) + .json({ error: 'Quota reached. Please buy API credits and try again later.' }); } } else { return response.status(405).json({ error: 'Invalid API Token!' }); diff --git a/apps/OpenSignServer/cloud/customRoute/v1/routes/createDocumentwithCoordinate.js b/apps/OpenSignServer/cloud/customRoute/v1/routes/createDocumentwithCoordinate.js index 82f792059..0278ab811 100644 --- a/apps/OpenSignServer/cloud/customRoute/v1/routes/createDocumentwithCoordinate.js +++ b/apps/OpenSignServer/cloud/customRoute/v1/routes/createDocumentwithCoordinate.js @@ -12,15 +12,10 @@ import { // `sendDoctoWebhook` is used to send res data of document on webhook async function sendDoctoWebhook(doc, WebhookUrl, userId) { if (WebhookUrl) { - const params = { - event: 'created', - ...doc, - }; + const params = { event: 'created', ...doc }; await axios - .post(WebhookUrl, params, { - headers: { 'Content-Type': 'application/json' }, - }) + .post(WebhookUrl, params, { headers: { 'Content-Type': 'application/json' } }) .then(res => { try { // console.log('res ', res); @@ -89,309 +84,336 @@ export default async function createDocumentwithCoordinate(request, response) { className: '_User', objectId: parseUser.userId.objectId, }; - if (signers && signers.length > 0) { - // Check if at least one signature exists among all items in the signers array - let isSignExist = signers.every(item => - item.widgets.some(data => data?.type === 'signature') - ); - if (!isSignExist) { - return response - .status(400) - .json({ error: 'Please add at least one signature widget for all signers' }); - } - let fileUrl; - if (request.files?.[0]) { - const base64 = fileData?.toString('base64'); - const file = new Parse.File(request.files?.[0]?.originalname, { - base64: base64, - }); - await file.save({ useMasterKey: true }); - fileUrl = file.url(); - const buffer = Buffer.from(base64, 'base64'); - saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId); - } else { - const filename = sanitizeFileName(`${name}.pdf`); - const file = new Parse.File(filename, { base64: base64File }, 'application/pdf'); - await file.save({ useMasterKey: true }); - fileUrl = file.url(); - const buffer = Buffer.from(base64File, 'base64'); - saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId); - } - const contractsUser = new Parse.Query('contracts_Users'); - contractsUser.equalTo('UserId', userPtr); - const extUser = await contractsUser.first({ useMasterKey: true }); - const parseExtUser = JSON.parse(JSON.stringify(extUser)); + const extUsers = new Parse.Query('contracts_Users'); + extUsers.equalTo('UserId', userPtr); + const extUser = await extUsers.first({ useMasterKey: true }); - const extUserPtr = { - __type: 'Pointer', - className: 'contracts_Users', - objectId: extUser.id, - }; - - const object = new Parse.Object('contracts_Document'); - object.set('Name', name); - - if (note) { - object.set('Note', note); - } - if (description) { - object.set('Description', description); - } - if (sendInOrder) { - object.set('SendinOrder', sendInOrder); - } - object.set('URL', fileUrl); - object.set('SignedUrl', fileUrl); - object.set('SentToOthers', true); - object.set('CreatedBy', userPtr); - object.set('ExtUserPtr', extUserPtr); - if (TimeToCompleteDays) { - object.set('TimeToCompleteDays', TimeToCompleteDays); - } - object.set('IsSendMail', send_email); - let contact = []; + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: extUser.get('TenantId').id, + }); + subscription.include('ExtUserPtr'); + subscription.greaterThan('AllowedApis', 0); + const resSub = await subscription.first({ useMasterKey: true }); + if (resSub) { if (signers && signers.length > 0) { - let parseSigners; - if (base64File) { - parseSigners = signers; - } else { - parseSigners = JSON.parse(signers); + // Check if at least one signature exists among all items in the signers array + let isSignExist = signers.every(item => + item.widgets.some(data => data?.type === 'signature') + ); + if (!isSignExist) { + return response + .status(400) + .json({ error: 'Please add at least one signature widget for all signers' }); } - let createContactUrl = protocol + '/v1/createcontact'; + let fileUrl; + if (request.files?.[0]) { + const base64 = fileData?.toString('base64'); + const file = new Parse.File(request.files?.[0]?.originalname, { + base64: base64, + }); + await file.save({ useMasterKey: true }); + fileUrl = file.url(); + const buffer = Buffer.from(base64, 'base64'); + saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId); + } else { + const filename = sanitizeFileName(`${name}.pdf`); + const file = new Parse.File(filename, { base64: base64File }, 'application/pdf'); + await file.save({ useMasterKey: true }); + fileUrl = file.url(); + const buffer = Buffer.from(base64File, 'base64'); + saveFileUsage(buffer.length, fileUrl, parseUser.userId.objectId); + } + const contractsUser = new Parse.Query('contracts_Users'); + contractsUser.equalTo('UserId', userPtr); + const extUser = await contractsUser.first({ useMasterKey: true }); + const parseExtUser = JSON.parse(JSON.stringify(extUser)); - for (const [index, element] of parseSigners.entries()) { - const body = { - name: element?.name || '', - email: element?.email || '', - phone: element?.phone || '', - }; - try { - const res = await axios.post(createContactUrl, body, { - headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken }, - }); - // console.log('res ', res.data); - const contactPtr = { - __type: 'Pointer', - className: 'contracts_Contactbook', - objectId: res.data?.objectId, + const extUserPtr = { + __type: 'Pointer', + className: 'contracts_Users', + objectId: extUser.id, + }; + + const object = new Parse.Object('contracts_Document'); + object.set('Name', name); + + if (note) { + object.set('Note', note); + } + if (description) { + object.set('Description', description); + } + if (sendInOrder) { + object.set('SendinOrder', sendInOrder); + } + object.set('URL', fileUrl); + object.set('SignedUrl', fileUrl); + object.set('SentToOthers', true); + object.set('CreatedBy', userPtr); + object.set('ExtUserPtr', extUserPtr); + if (TimeToCompleteDays) { + object.set('TimeToCompleteDays', TimeToCompleteDays); + } + object.set('IsSendMail', send_email); + let contact = []; + if (signers && signers.length > 0) { + let parseSigners; + if (base64File) { + parseSigners = signers; + } else { + parseSigners = JSON.parse(signers); + } + let createContactUrl = protocol + '/v1/createcontact'; + + for (const [index, element] of parseSigners.entries()) { + const body = { + name: element?.name || '', + email: element?.email || '', + phone: element?.phone || '', }; - const newObj = { ...element, contactPtr: contactPtr, index: index }; - contact.push(newObj); - } catch (err) { - // console.log('err ', err.response); - if (err?.response?.data?.objectId) { + try { + const res = await axios.post(createContactUrl, body, { + headers: { 'Content-Type': 'application/json', 'x-api-token': reqToken }, + }); + // console.log('res ', res.data); const contactPtr = { __type: 'Pointer', className: 'contracts_Contactbook', - objectId: err.response.data?.objectId, + objectId: res.data?.objectId, }; const newObj = { ...element, contactPtr: contactPtr, index: index }; contact.push(newObj); + } catch (err) { + // console.log('err ', err.response); + if (err?.response?.data?.objectId) { + const contactPtr = { + __type: 'Pointer', + className: 'contracts_Contactbook', + objectId: err.response.data?.objectId, + }; + const newObj = { ...element, contactPtr: contactPtr, index: index }; + contact.push(newObj); + } } } - } - object.set( - 'Signers', - contact?.map(x => x.contactPtr) - ); - let updatePlaceholders = contact.map(signer => { - const placeHolder = []; + object.set( + 'Signers', + contact?.map(x => x.contactPtr) + ); + let updatePlaceholders = contact.map(signer => { + const placeHolder = []; - for (const widget of signer.widgets) { - const pageNumber = widget.page; - const options = formatWidgetOptions(widget.type, widget.options); - const page = placeHolder.find(page => page.pageNumber === pageNumber); - const widgetData = { - isStamp: widget.type === 'stamp' || widget.type === 'image', - key: randomId(), - isDrag: false, - scale: 1, - isMobile: false, - zIndex: 1, - type: widget.type === 'textbox' ? 'text input' : widget.type, - options: options, - Width: widget.w, - Height: widget.h, - xPosition: widget.x, - yPosition: widget.y, - }; - - if (page) { - page.pos.push(widgetData); - } else { - placeHolder.push({ - pageNumber, - pos: [widgetData], - }); - } - } - - return { - signerObjId: signer?.contactPtr?.objectId, - signerPtr: signer?.contactPtr, - Role: signer.role, - Id: randomId(), - blockColor: color[signer?.index], - placeHolder, - }; - }); - object.set('Placeholders', updatePlaceholders); - } - if (folderId) { - object.set('Folder', { - __type: 'Pointer', - className: 'contracts_Document', - objectId: folderId, - }); - } - const newACL = new Parse.ACL(); - newACL.setPublicReadAccess(false); - newACL.setPublicWriteAccess(false); - newACL.setReadAccess(userPtr.objectId, true); - newACL.setWriteAccess(userPtr.objectId, true); - object.setACL(newACL); - const res = await object.save(null, { useMasterKey: true }); - const doc = { - objectId: res.id, - file: fileUrl, - name: name, - note: note || '', - description: description || '', - signers: contact?.map(x => ({ name: x.name, email: x.email, phone: x?.phone || '' })), - createdAt: res.createdAt, - }; - if (parseExtUser && parseExtUser.Webhook) { - sendDoctoWebhook(doc, parseExtUser?.Webhook, userPtr?.objectId); - } - const newDate = new Date(); - newDate.setDate(newDate.getDate() + 15); - const localExpireDate = newDate.toLocaleDateString('en-US', { - day: 'numeric', - month: 'long', - year: 'numeric', - }); - let sender = parseExtUser.Email; - if (send_email === false) { - console.log("don't send mail"); - } else { - let contactMail = contact; - if (sendInOrder) { - contactMail = contact.slice(); - contactMail.splice(1); - } - for (let i = 0; i < contactMail.length; i++) { - try { - const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png'; - let url = `${cloudServerUrl}/functions/sendmailv3/`; - const headers = { - 'Content-Type': 'application/json', - 'X-Parse-Application-Id': process.env.APP_ID, - 'X-Parse-Master-Key': process.env.MASTER_KEY, - }; - - const objectId = contactMail[i].contactPtr.objectId; - const hostUrl = baseUrl.origin; - //encode this url value `${response.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function - const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`); - let signPdf = `${hostUrl}/login/${encodeBase64}`; - const openSignUrl = 'https://www.opensignlabs.com/contact-us'; - const orgName = parseExtUser.Company ? parseExtUser.Company : ''; - const themeBGcolor = '#47a3ad'; - const email_html = - "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" + - imgPng + - " height='50' style='padding:20px; width:170px; height:40px;' /></div> <div style='padding:2px; font-family: system-ui;background-color:" + - themeBGcolor + - ";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " + - parseExtUser.Name + - ' has requested you to review and sign <strong> ' + - name + - "</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" + - sender + - "</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " + - orgName + - "</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" + - localExpireDate + - "</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" + - signPdf + - "> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + - sender + - ' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' + - openSignUrl + - ' target=_blank>here</a>.</p> </div></div></body> </html>'; - let replaceVar; - const variables = { - document_title: name, - sender_name: parseExtUser.Name, - sender_mail: parseExtUser.Email, - sender_phone: parseExtUser?.Phone || '', - receiver_name: contactMail[i].name, - receiver_email: contactMail[i].email, - receiver_phone: contactMail[i]?.phone || '', - expiry_date: localExpireDate, - company_name: orgName, - signing_url: signPdf, - }; - if (email_subject && email_body) { - replaceVar = replaceMailVaribles(email_subject, email_body, variables); - } else if (email_subject) { - replaceVar = replaceMailVaribles(email_subject, '', variables); - replaceVar = { subject: replaceVar.subject, body: email_html }; - } else if (email_body) { - replaceVar = replaceMailVaribles( - `${parseExtUser.Name} has requested you to sign "${name}"`, - email_body, - variables - ); - } else { - replaceVar = { - subject: `${parseExtUser.Name} has requested you to sign "${name}"`, - body: email_html, + for (const widget of signer.widgets) { + const pageNumber = widget.page; + const options = formatWidgetOptions(widget.type, widget.options); + const page = placeHolder.find(page => page.pageNumber === pageNumber); + const widgetData = { + isStamp: widget.type === 'stamp' || widget.type === 'image', + key: randomId(), + isDrag: false, + scale: 1, + isMobile: false, + zIndex: 1, + type: widget.type === 'textbox' ? 'text input' : widget.type, + options: options, + Width: widget.w, + Height: widget.h, + xPosition: widget.x, + yPosition: widget.y, }; + + if (page) { + page.pos.push(widgetData); + } else { + placeHolder.push({ pageNumber, pos: [widgetData] }); + } } - const subject = replaceVar.subject; - const html = replaceVar.body; - let params = { - recipient: contactMail[i].email, - subject: subject, - from: sender, - html: html, - extUserId: extUser.id, + return { + signerObjId: signer?.contactPtr?.objectId, + signerPtr: signer?.contactPtr, + Role: signer.role, + Id: randomId(), + blockColor: color[signer?.index], + placeHolder, }; + }); + object.set('Placeholders', updatePlaceholders); + } + if (folderId) { + object.set('Folder', { + __type: 'Pointer', + className: 'contracts_Document', + objectId: folderId, + }); + } + const newACL = new Parse.ACL(); + newACL.setPublicReadAccess(false); + newACL.setPublicWriteAccess(false); + newACL.setReadAccess(userPtr.objectId, true); + newACL.setWriteAccess(userPtr.objectId, true); + object.setACL(newACL); + const res = await object.save(null, { useMasterKey: true }); + const doc = { + objectId: res.id, + file: fileUrl, + name: name, + note: note || '', + description: description || '', + signers: contact?.map(x => ({ name: x.name, email: x.email, phone: x?.phone || '' })), + createdAt: res.createdAt, + }; + if (parseExtUser && parseExtUser.Webhook) { + sendDoctoWebhook(doc, parseExtUser?.Webhook, userPtr?.objectId); + } + const newDate = new Date(); + newDate.setDate(newDate.getDate() + 15); + const localExpireDate = newDate.toLocaleDateString('en-US', { + day: 'numeric', + month: 'long', + year: 'numeric', + }); + let sender = parseExtUser.Email; + if (send_email === false) { + console.log("don't send mail"); + } else { + let contactMail = contact; + if (sendInOrder) { + contactMail = contact.slice(); + contactMail.splice(1); + } + for (let i = 0; i < contactMail.length; i++) { + try { + const imgPng = 'https://qikinnovation.ams3.digitaloceanspaces.com/logo.png'; + let url = `${cloudServerUrl}/functions/sendmailv3/`; + const headers = { + 'Content-Type': 'application/json', + 'X-Parse-Application-Id': process.env.APP_ID, + 'X-Parse-Master-Key': process.env.MASTER_KEY, + }; - await axios.post(url, params, { headers: headers }); - } catch (error) { - console.log('error', error); + const objectId = contactMail[i].contactPtr.objectId; + const hostUrl = baseUrl.origin; + //encode this url value `${response.id}/${contactMail[i].email}/${objectId}` to base64 using `btoa` function + const encodeBase64 = btoa(`${res.id}/${contactMail[i].email}/${objectId}`); + let signPdf = `${hostUrl}/login/${encodeBase64}`; + const openSignUrl = 'https://www.opensignlabs.com/contact-us'; + const orgName = parseExtUser.Company ? parseExtUser.Company : ''; + const themeBGcolor = '#47a3ad'; + const email_html = + "<html><head><meta http-equiv='Content-Type' content='text/html; charset=UTF-8' /> </head> <body> <div style='background-color: #f5f5f5; padding: 20px'> <div style=' box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 12px;background: white;padding-bottom: 20px;'> <div style='padding:10px 10px 0 10px'><img src=" + + imgPng + + " height='50' style='padding:20px; width:170px; height:40px;' /></div> <div style='padding:2px; font-family: system-ui;background-color:" + + themeBGcolor + + ";'><p style='font-size: 20px;font-weight: 400;color: white;padding-left: 20px;' > Digital Signature Request</p></div><div><p style='padding: 20px;font-family: system-ui;font-size: 14px; margin-bottom: 10px;'> " + + parseExtUser.Name + + ' has requested you to review and sign <strong> ' + + name + + "</strong>.</p><div style='padding: 5px 0px 5px 25px;display: flex;flex-direction: row;justify-content: space-around;'><table> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Sender</td> <td> </td> <td style='color:#626363;font-weight:bold'>" + + sender + + "</td></tr><tr><td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Organization</td> <td> </td><td style='color:#626363;font-weight:bold'> " + + orgName + + "</td></tr> <tr> <td style='font-weight:bold;font-family:sans-serif;font-size:15px'>Expires on</td><td> </td> <td style='color:#626363;font-weight:bold'>" + + localExpireDate + + "</td></tr><tr> <td></td> <td> </td></tr></table> </div> <div style='margin-left:70px'><a href=" + + signPdf + + "> <button style='padding: 12px 12px 12px 12px;background-color: #d46b0f;color: white; border: 0px;box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px,rgba(0, 0, 0, 0.08) 0px 0px 0px 1px;font-weight:bold;margin-top:30px'>Sign here</button></a> </div> <div style='display: flex; justify-content: center;margin-top: 10px;'> </div></div></div><div><p> This is an automated email from OpenSign™. For any queries regarding this email, please contact the sender " + + sender + + ' directly.If you think this email is inappropriate or spam, you may file a complaint with OpenSign™ <a href=' + + openSignUrl + + ' target=_blank>here</a>.</p> </div></div></body> </html>'; + let replaceVar; + const variables = { + document_title: name, + sender_name: parseExtUser.Name, + sender_mail: parseExtUser.Email, + sender_phone: parseExtUser?.Phone || '', + receiver_name: contactMail[i].name, + receiver_email: contactMail[i].email, + receiver_phone: contactMail[i]?.phone || '', + expiry_date: localExpireDate, + company_name: orgName, + signing_url: signPdf, + }; + if (email_subject && email_body) { + replaceVar = replaceMailVaribles(email_subject, email_body, variables); + } else if (email_subject) { + replaceVar = replaceMailVaribles(email_subject, '', variables); + replaceVar = { subject: replaceVar.subject, body: email_html }; + } else if (email_body) { + replaceVar = replaceMailVaribles( + `${parseExtUser.Name} has requested you to sign "${name}"`, + email_body, + variables + ); + } else { + replaceVar = { + subject: `${parseExtUser.Name} has requested you to sign "${name}"`, + body: email_html, + }; + } + const subject = replaceVar.subject; + const html = replaceVar.body; + + let params = { + recipient: contactMail[i].email, + subject: subject, + from: sender, + html: html, + extUserId: extUser.id, + }; + + await axios.post(url, params, { headers: headers }); + } catch (error) { + console.log('error', error); + } } } - } - // if (sendMail.data.result.status === 'success') { - if (request.posthog) { - request.posthog?.capture({ - distinctId: parseUser.userId.email, - event: 'api_create_document', - properties: { response_code: 200 }, + // if (sendMail.data.result.status === 'success') { + if (request.posthog) { + request.posthog?.capture({ + distinctId: parseUser.userId.email, + event: 'api_create_document', + properties: { response_code: 200 }, + }); + } + const updateApiCount = + resSub?.get('AllowedApis') && resSub.get('AllowedApis') > 0 + ? resSub.get('AllowedApis') - 1 + : 0; + const subscriptionCls = new Parse.Object('contracts_Subscriptions'); + subscriptionCls.id = resSub.id; + subscriptionCls.set('AllowedApis', updateApiCount); + const resSubcription = await subscriptionCls.save(null, { useMasterKey: true }); + // console.log('resSubcription ', resSubcription); + return response.json({ + objectId: res.id, + signurl: contact.map(x => ({ + email: x.email, + url: `${baseUrl.origin}/login/${btoa( + `${res.id}/${x.email}/${x.contactPtr.objectId}` + )}`, + })), + message: 'Document sent successfully!', }); + // } + } else { + if (request.posthog) { + request.posthog?.capture({ + distinctId: parseUser.userId.email, + event: 'api_create_document', + properties: { response_code: 400 }, + }); + } + return response.status(400).json({ error: 'Please provide signers!' }); } - return response.json({ - objectId: res.id, - signurl: contact.map(x => ({ - email: x.email, - url: `${baseUrl.origin}/login/${btoa(`${res.id}/${x.email}/${x.contactPtr.objectId}`)}`, - })), - message: 'Document sent successfully!', - }); - // } } else { - if (request.posthog) { - request.posthog?.capture({ - distinctId: parseUser.userId.email, - event: 'api_create_document', - properties: { response_code: 400 }, - }); - } - return response.status(400).json({ error: 'Please provide signers!' }); + return response + .status(429) + .json({ error: 'Quota reached. Please buy API credits and try again later.' }); } } else { return response.status(405).json({ error: 'Invalid API Token!' }); diff --git a/apps/OpenSignServer/cloud/main.js b/apps/OpenSignServer/cloud/main.js index 9672e33bd..fc83e124e 100644 --- a/apps/OpenSignServer/cloud/main.js +++ b/apps/OpenSignServer/cloud/main.js @@ -62,6 +62,8 @@ import getOrgAdmins from './parsefunction/getOrgAdmins.js'; import getAllUserTeamByOrg from './parsefunction/getAllUserTeamByOrg.js'; import AllowedUsers from './parsefunction/AlllowedUsers.js'; import Buyaddon from './parsefunction/BuyAddon.js'; +import AllowedApis from './parsefunction/AllowedApis.js'; +import BuyApis from './parsefunction/BuyApis.js'; // This afterSave function triggers after an object is added or updated in the specified class, allowing for post-processing logic. Parse.Cloud.afterSave('contracts_Document', DocumentAftersave); @@ -133,3 +135,5 @@ Parse.Cloud.define('getorgadmins', getOrgAdmins); Parse.Cloud.define('getalluserteambyorg', getAllUserTeamByOrg); Parse.Cloud.define('allowedusers', AllowedUsers); Parse.Cloud.define('buyaddon', Buyaddon); +Parse.Cloud.define('allowedapis', AllowedApis); +Parse.Cloud.define('buyapis', BuyApis); diff --git a/apps/OpenSignServer/cloud/parsefunction/AllowedApis.js b/apps/OpenSignServer/cloud/parsefunction/AllowedApis.js new file mode 100644 index 000000000..878d1f099 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/AllowedApis.js @@ -0,0 +1,35 @@ +export default async function AllowedApis(request) { + if (!request?.user) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); + } + try { + const extUser = new Parse.Query('contracts_Users'); + extUser.equalTo('UserId', { + __type: 'Pointer', + className: '_User', + objectId: request.user.id, + }); + const resExtUser = await extUser.first({ useMasterKey: true }); + if (resExtUser) { + const _resExtUser = JSON.parse(JSON.stringify(resExtUser)); + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: _resExtUser.TenantId.objectId, + }); + subscription.include('ExtUserPtr'); + const resSub = await subscription.first({ useMasterKey: true }); + if (resSub) { + const _resSub = JSON.parse(JSON.stringify(resSub)); + const allowedapis = _resSub?.AllowedApis || 0; + return allowedapis; + } + } + } catch (err) { + console.log('err in allowedapis', err); + const code = err?.code || 400; + const msg = err?.message || 'Something went wrong.'; + throw new Parse.Error(code, msg); + } +} diff --git a/apps/OpenSignServer/cloud/parsefunction/BuyApis.js b/apps/OpenSignServer/cloud/parsefunction/BuyApis.js new file mode 100644 index 000000000..2c0584642 --- /dev/null +++ b/apps/OpenSignServer/cloud/parsefunction/BuyApis.js @@ -0,0 +1,87 @@ +import axios from 'axios'; +export default async function BuyApis(request) { + const apis = request.params.apis; + if (!request?.user) { + throw new Parse.Error(Parse.Error.INVALID_SESSION_TOKEN, 'User is not authenticated.'); + } + if (apis) { + try { + const extUser = new Parse.Query('contracts_Users'); + extUser.equalTo('UserId', { + __type: 'Pointer', + className: '_User', + objectId: request.user.id, + }); + const resExtUser = await extUser.first({ useMasterKey: true }); + if (resExtUser) { + const _resExtUser = JSON.parse(JSON.stringify(resExtUser)); + const subscription = new Parse.Query('contracts_Subscriptions'); + subscription.equalTo('TenantId', { + __type: 'Pointer', + className: 'partners_Tenant', + objectId: _resExtUser.TenantId.objectId, + }); + subscription.include('ExtUserPtr'); + const resSub = await subscription.first({ useMasterKey: true }); + if (resSub) { + const _resSub = JSON.parse(JSON.stringify(resSub)); + // Define the URL + const url = 'https://accounts.zoho.in/oauth/v2/token'; + + // Convert the data to x-www-form-urlencoded format + const formData = new URLSearchParams(); + formData.append('refresh_token', process.env.ZOHO_REFRESH_TOKEN); + formData.append('client_id', process.env.ZOHO_CLIENT_ID); + formData.append('client_secret', process.env.ZOHO_CLIENT_SECRET); + formData.append('redirect_uri', process.env.ZOHO_REDIRECT_URI); + formData.append('grant_type', 'refresh_token'); + + const headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; + // Make the POST request using Axios + const res = await axios.post(url, formData, { headers }); + if (res.data.access_token) { + const subscriptionId = _resSub.SubscriptionId; + const plan_code = _resSub?.SubscriptionDetails?.data?.subscription?.plan?.plan_code; + const quantity = parseInt(apis); + const addoncode = 'api-signatures'; + const data = JSON.stringify({ + plan: { plan_code: plan_code }, + addons: [ + { addon_code: addoncode, addon_description: 'API Signatures', quantity: quantity }, + ], + }); + const apiUrl = `https://www.zohoapis.in/billing/v1/subscriptions/${subscriptionId}/buyonetimeaddon`; + const resAPis = await axios.post(apiUrl, data, { + headers: { + Authorization: 'Zoho-oauthtoken ' + res.data.access_token, + 'X-com-zoho-subscriptions-organizationid': process.env.ZOHO_BILLING_ORG_ID, + }, + }); + // console.log('resAPis ', resAPis.data); + if (resAPis.data) { + const updateSub = new Parse.Object('contracts_Subscriptions'); + updateSub.id = resSub.id; + // updateSub.set('AllowedApis', quantity); + const resupdateSub = await updateSub.save(null, { useMasterKey: true }); + // console.log('resupdateSub ', resupdateSub); + return { status: 'success', addon: quantity }; + } + } else { + throw new Parse.Error('400', 'Invalid access token.'); + } + } + } + } catch (err) { + const code = err?.response?.data?.code || err?.response?.status || err?.code || 400; + const msg = + err?.response?.data?.error || + err?.response?.data || + err?.message || + 'Something went wrong.'; + console.log('err in buyaddon', code, msg); + throw new Parse.Error(code, msg); + } + } else { + throw new Parse.Error(Parse.Error.INVALID_QUERY, 'Please provide parameters.'); + } +} diff --git a/apps/OpenSignServer/cloud/parsefunction/getapitoken.js b/apps/OpenSignServer/cloud/parsefunction/getapitoken.js index ac78e8ab0..b76217f85 100644 --- a/apps/OpenSignServer/cloud/parsefunction/getapitoken.js +++ b/apps/OpenSignServer/cloud/parsefunction/getapitoken.js @@ -1,29 +1,24 @@ -import axios from 'axios'; -import { cloudServerUrl } from '../../Utils.js'; export default async function getapitoken(request) { - const serverUrl = cloudServerUrl; //process.env.SERVER_URL; try { - const userRes = await axios.get(serverUrl + '/users/me', { - headers: { - 'X-Parse-Application-Id': process.env.APP_ID, - 'X-Parse-Session-Token': request.headers['sessiontoken'], - }, - }); - const userId = userRes.data && userRes.data.objectId; - if (userId) { + if (request?.user) { + const userId = request?.user?.id; const tokenQuery = new Parse.Query('appToken'); tokenQuery.equalTo('userId', { __type: 'Pointer', className: '_User', objectId: userId }); const res = await tokenQuery.first({ useMasterKey: true }); if (res) { return { status: 'success', result: res.get('token') }; + } else { + return { error: 'api token found.' }; } + } else { + return { error: 'Invalid session token.' }; } } catch (err) { console.log('Err in getapitoken', err); if (err.code == 209) { - return { error: 'Invalid session token' }; + return { error: 'Invalid session token.' }; } else { - return { error: "You don't have access!" }; + return { error: "You don't have access." }; } } }