|
1 | 1 | const { google } = require('googleapis'); |
2 | 2 | const postmark = require('postmark'); |
3 | 3 |
|
| 4 | +// === Utility Functions === |
| 5 | + |
| 6 | +const createEmailHtml = function (submission) { |
| 7 | + let html = '<ul>'; |
| 8 | + // Create a safe copy of submission object to iterate through |
| 9 | + const safeSubmission = { ...submission }; |
| 10 | + // Use a safer method to avoid object injection |
| 11 | + const htmlItems = Object.entries(safeSubmission).map( |
| 12 | + ([key, value]) => `<li><strong>${key}:</strong> ${value}</li>`, |
| 13 | + ); |
| 14 | + html += htmlItems.join(''); |
| 15 | + html += '</ul>'; |
| 16 | + return html; |
| 17 | +}; |
| 18 | + |
| 19 | +const createPostmarkClient = function (apiKey) { |
| 20 | + return new postmark.ServerClient(apiKey); |
| 21 | +}; |
| 22 | + |
| 23 | +const findFieldValue = function (submission, fieldName) { |
| 24 | + if (fieldName && submission[fieldName] !== undefined) { |
| 25 | + return submission[fieldName]; |
| 26 | + } |
| 27 | + return null; |
| 28 | +}; |
| 29 | + |
| 30 | +const prepareSheetData = function (submission) { |
| 31 | + const id = Date.now().toString(); |
| 32 | + return [id, new Date().toISOString(), ...Object.values(submission)]; |
| 33 | +}; |
| 34 | + |
| 35 | +const createSheetsClient = function (form) { |
| 36 | + const auth = new google.auth.JWT({ |
| 37 | + email: form.serviceAccountEmail, |
| 38 | + key: form.serviceAccountPrivateKey.replace(/\\n/gu, '\n'), |
| 39 | + scopes: ['https://www.googleapis.com/auth/spreadsheets'], |
| 40 | + }); |
| 41 | + |
| 42 | + return google.sheets({ version: 'v4', auth }); |
| 43 | +}; |
| 44 | + |
| 45 | +const createSendEmailFunction = function (self, postmarkClient) { |
| 46 | + return async (from, to, subject, htmlBody) => { |
| 47 | + try { |
| 48 | + const response = await postmarkClient.sendEmail({ |
| 49 | + From: from, |
| 50 | + To: to, |
| 51 | + Subject: subject, |
| 52 | + HtmlBody: htmlBody, |
| 53 | + MessageStream: 'outbound', |
| 54 | + }); |
| 55 | + self.apos.util.log(`Email sent successfully to ${to}`); |
| 56 | + if (response.ErrorCode) { |
| 57 | + self.apos.util.error(response.ErrorCode); |
| 58 | + } |
| 59 | + } catch (error) { |
| 60 | + self.apos.util.error(`Error sending email to ${to}`, error); |
| 61 | + } |
| 62 | + }; |
| 63 | +}; |
| 64 | + |
| 65 | +const sendConfirmationEmail = async function ( |
| 66 | + self, |
| 67 | + form, |
| 68 | + submission, |
| 69 | + sendEmailFunc, |
| 70 | +) { |
| 71 | + const confirmationFieldName = form.emailConfirmationField; |
| 72 | + const senderEmail = findFieldValue(submission, confirmationFieldName); |
| 73 | + if (!senderEmail) { |
| 74 | + self.apos.util.warn( |
| 75 | + `Email confirmation field "${form.emailConfirmationField}" not found in the submission.`, |
| 76 | + ); |
| 77 | + return false; |
| 78 | + } |
| 79 | + const confirmationHtml = |
| 80 | + '<p>Thank you for your submission! We will review your message as soon as possible.</p>'; |
| 81 | + await sendEmailFunc( |
| 82 | + form.fromEmail, |
| 83 | + senderEmail, |
| 84 | + 'Confirmation of Form Submission from Procrea', |
| 85 | + confirmationHtml, |
| 86 | + ); |
| 87 | + return true; |
| 88 | +}; |
| 89 | + |
| 90 | +const handlePostmark = async function (self, form, submission) { |
| 91 | + const emailSubject = `${form.title} Form (${form.domainName || 'defaultdomain.com'})`; |
| 92 | + const html = self.createEmailHtml(submission); |
| 93 | + const postmarkClient = self.createPostmarkClient(form.postmarkApiKey); |
| 94 | + const sendPostmarkEmail = self.createSendEmailFunction(postmarkClient); |
| 95 | + |
| 96 | + try { |
| 97 | + await sendPostmarkEmail(form.fromEmail, form.toEmail, emailSubject, html); |
| 98 | + if (form.sendConfirmationEmail) { |
| 99 | + await self.sendConfirmationEmail(form, submission, sendPostmarkEmail); |
| 100 | + } |
| 101 | + } catch (error) { |
| 102 | + self.apos.util.error('Error processing email sending', error); |
| 103 | + } |
| 104 | +}; |
| 105 | + |
| 106 | +const handleSpreadsheet = async function (self, form, submission) { |
| 107 | + try { |
| 108 | + const sheets = self.createSheetsClient(form); |
| 109 | + const values = self.prepareSheetData(submission); |
| 110 | + const resource = { values: [values] }; |
| 111 | + |
| 112 | + await sheets.spreadsheets.values.append({ |
| 113 | + spreadsheetId: form.spreadsheetId, |
| 114 | + range: 'Sheet1!A1', |
| 115 | + valueInputOption: 'RAW', |
| 116 | + resource, |
| 117 | + }); |
| 118 | + self.apos.util.log('Data inserted into Google Sheets successfully.'); |
| 119 | + } catch (error) { |
| 120 | + self.apos.util.error('Error Sheets data insertion', error); |
| 121 | + } |
| 122 | +}; |
| 123 | + |
| 124 | +const processSubmission = async function (self, form, submission) { |
| 125 | + if (form.enablePostmark) { |
| 126 | + await self.handlePostmark(form, submission); |
| 127 | + } |
| 128 | + |
| 129 | + if (form.enableSpreadsheet) { |
| 130 | + await self.handleSpreadsheet(form, submission); |
| 131 | + } |
| 132 | +}; |
| 133 | + |
| 134 | +// === Module Export === |
| 135 | + |
4 | 136 | module.exports = { |
5 | 137 | options: { |
6 | 138 | emailSubmissions: false, |
@@ -120,147 +252,23 @@ module.exports = { |
120 | 252 | }, |
121 | 253 | }, |
122 | 254 |
|
123 | | - // Helper methods for email and spreadsheet functionality |
124 | | - createEmailHtml(submission) { |
125 | | - let html = '<ul>'; |
126 | | - // Create a safe copy of submission object to iterate through |
127 | | - const safeSubmission = { ...submission }; |
128 | | - // Use a safer method to avoid object injection |
129 | | - const htmlItems = Object.entries(safeSubmission).map( |
130 | | - ([key, value]) => `<li><strong>${key}:</strong> ${value}</li>`, |
131 | | - ); |
132 | | - html += htmlItems.join(''); |
133 | | - html += '</ul>'; |
134 | | - return html; |
135 | | - }, |
136 | | - |
137 | | - createPostmarkClient(apiKey) { |
138 | | - return new postmark.ServerClient(apiKey); |
139 | | - }, |
140 | | - |
141 | | - findFieldValue(submission, fieldName) { |
142 | | - if (!fieldName) { |
143 | | - return null; |
144 | | - } |
145 | | - |
146 | | - // Use direct property access instead of iteration |
147 | | - if (submission[fieldName] !== undefined) { |
148 | | - return submission[fieldName]; |
149 | | - } |
150 | | - |
151 | | - return null; |
152 | | - }, |
153 | | - |
154 | | - prepareSheetData(submission) { |
155 | | - const id = Date.now().toString(); |
156 | | - return [id, new Date().toISOString(), ...Object.values(submission)]; |
157 | | - }, |
158 | | - |
159 | | - createSheetsClient(form) { |
160 | | - const auth = new google.auth.JWT({ |
161 | | - email: form.serviceAccountEmail, |
162 | | - key: form.serviceAccountPrivateKey.replace(/\\n/gu, '\n'), |
163 | | - scopes: ['https://www.googleapis.com/auth/spreadsheets'], |
164 | | - }); |
165 | | - |
166 | | - return google.sheets({ |
167 | | - version: 'v4', |
168 | | - auth, |
169 | | - }); |
170 | | - }, |
171 | | - |
172 | | - createSendEmailFunction(postmarkClient) { |
173 | | - const { apos } = this; |
174 | | - return async (from, to, subject, htmlBody) => { |
175 | | - try { |
176 | | - const response = await postmarkClient.sendEmail({ |
177 | | - From: from, |
178 | | - To: to, |
179 | | - Subject: subject, |
180 | | - HtmlBody: htmlBody, |
181 | | - MessageStream: 'outbound', |
182 | | - }); |
183 | | - apos.util.log(`Email sent successfully to ${to}`); |
184 | | - if (response.ErrorCode) { |
185 | | - apos.util.error(response.ErrorCode); |
186 | | - } |
187 | | - } catch (error) { |
188 | | - apos.util.error(`Error sending email to ${to}`, error); |
189 | | - } |
| 255 | + methods(self) { |
| 256 | + return { |
| 257 | + createEmailHtml, |
| 258 | + createPostmarkClient, |
| 259 | + findFieldValue, |
| 260 | + prepareSheetData, |
| 261 | + createSheetsClient, |
| 262 | + createSendEmailFunction: (...args) => |
| 263 | + createSendEmailFunction(self, ...args), |
| 264 | + sendConfirmationEmail: (...args) => sendConfirmationEmail(self, ...args), |
| 265 | + handlePostmark: (...args) => handlePostmark(self, ...args), |
| 266 | + handleSpreadsheet: (...args) => handleSpreadsheet(self, ...args), |
| 267 | + processSubmission: (...args) => processSubmission(self, ...args), |
190 | 268 | }; |
191 | 269 | }, |
192 | 270 |
|
193 | | - async sendConfirmationEmail(form, submission, sendEmailFunc) { |
194 | | - const confirmationFieldName = form.emailConfirmationField; |
195 | | - const senderEmail = this.findFieldValue(submission, confirmationFieldName); |
196 | | - if (!senderEmail) { |
197 | | - this.apos.util.warn( |
198 | | - `Email confirmation field "${form.emailConfirmationField}" not found in the submission.`, |
199 | | - ); |
200 | | - return false; |
201 | | - } |
202 | | - const confirmationHtml = |
203 | | - '<p>Thank you for your submission! We will review your message as soon as possible.</p>'; |
204 | | - await sendEmailFunc( |
205 | | - form.fromEmail, |
206 | | - senderEmail, |
207 | | - 'Confirmation of Form Submission from Procrea', |
208 | | - confirmationHtml, |
209 | | - ); |
210 | | - return true; |
211 | | - }, |
212 | | - |
213 | | - async handlePostmark(form, submission) { |
214 | | - const emailSubject = `${form.title} Form (${form.domainName || 'defaultdomain.com'})`; |
215 | | - const html = this.createEmailHtml(submission); |
216 | | - const postmarkClient = this.createPostmarkClient(form.postmarkApiKey); |
217 | | - const sendPostmarkEmail = this.createSendEmailFunction(postmarkClient); |
218 | | - |
219 | | - try { |
220 | | - // Send the main email |
221 | | - await sendPostmarkEmail(form.fromEmail, form.toEmail, emailSubject, html); |
222 | | - |
223 | | - // Send confirmation email if configured |
224 | | - if (form.sendConfirmationEmail) { |
225 | | - await this.sendConfirmationEmail(form, submission, sendPostmarkEmail); |
226 | | - } |
227 | | - } catch (error) { |
228 | | - this.apos.util.error('Error processing email sending', error); |
229 | | - } |
230 | | - }, |
231 | | - |
232 | | - async handleSpreadsheet(form, submission) { |
233 | | - try { |
234 | | - const { spreadsheetId } = form; |
235 | | - const range = 'Sheet1!A1'; |
236 | | - const sheets = this.createSheetsClient(form); |
237 | | - const values = this.prepareSheetData(submission); |
238 | | - const resource = { values: [values] }; |
239 | | - |
240 | | - await sheets.spreadsheets.values.append({ |
241 | | - spreadsheetId, |
242 | | - range, |
243 | | - valueInputOption: 'RAW', |
244 | | - resource, |
245 | | - }); |
246 | | - this.apos.util.log('Data inserted into Google Sheets successfully.'); |
247 | | - } catch (error) { |
248 | | - this.apos.util.error('Error Sheets data insertion', error); |
249 | | - } |
250 | | - }, |
251 | | - |
252 | | - async processSubmission(req, form, submission) { |
253 | | - if (form.enablePostmark) { |
254 | | - await this.handlePostmark(form, submission); |
255 | | - } |
256 | | - |
257 | | - if (form.enableSpreadsheet) { |
258 | | - await this.handleSpreadsheet(form, submission); |
259 | | - } |
260 | | - }, |
261 | | - |
262 | 271 | init(self) { |
263 | | - // Register the event handler using the named function |
264 | | - self.on('submission', self.processSubmission); |
| 272 | + self.on('submission', 'processSubmission'); |
265 | 273 | }, |
266 | 274 | }; |
0 commit comments