diff --git a/functions/src/index.ts b/functions/src/index.ts index b250098..4c98cab 100644 --- a/functions/src/index.ts +++ b/functions/src/index.ts @@ -26,147 +26,158 @@ const corsHandler = cors({ origin: true }); export const sendEmailWithAttachment = onRequest({ region: '#{SERVICES_RGN}#' }, async (request: Request, response: express.Response) => { - try { - const { toAddress, subject, message, fileUrl, fileName } = request.body; + return corsHandler(request, response, async () => { + try { + const { toAddress, subject, message, fileUrl, fileName } = request.body; - if (!toAddress || !subject || !message || !fileUrl) { - response.status(400).json({ - error: 'Missing required fields (toAddress, subject, message, fileUrl)' - }); - return; - } - const tempFilePath = path.join(os.tmpdir(), fileName || 'attachment.pdf'); - await new Promise((resolve, reject) => { - const file = fs.createWriteStream(tempFilePath); - https.get(fileUrl, (res) => { - res.pipe(file); - file.on('finish', () => { - file.close(); - resolve(); + if (!toAddress || !subject || !message || !fileUrl) { + response.status(400).json({ + error: 'Missing required fields (toAddress, subject, message, fileUrl)' }); - }).on('error', (err) => { - fs.unlink(tempFilePath, () => { }); - reject(err); - }); - }); - - const mailgun = new Mailgun(formData); - const client = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY }); - - const options = { - wordwrap: 130, - }; - const textMessage = convert(message, options); - const fileBuffer = fs.readFileSync(tempFilePath); - const attachmentFilename = fileName || path.basename(fileUrl.split('?')[0]); - - const data = { - from: process.env.MAILGUN_FROM_ADDRESS, - to: toAddress, - subject: subject, - text: textMessage, - html: message, - attachment: { - data: fileBuffer, - filename: attachmentFilename, - contentType: 'application/pdf', + return; } - }; + const tempFilePath = path.join(os.tmpdir(), fileName || 'attachment.pdf'); + await new Promise((resolve, reject) => { + const file = fs.createWriteStream(tempFilePath); + https.get(fileUrl, (res) => { + res.pipe(file); + file.on('finish', () => { + file.close(); + resolve(); + }); + }).on('error', (err) => { + fs.unlink(tempFilePath, () => { }); + reject(err); + }); + }); - const result = await client.messages.create(process.env.MAILGUN_SERVER, data); - fs.unlinkSync(tempFilePath); + const mailgun = new Mailgun(formData); + const client = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY }); - logger.info('Email with attachment from URL sent successfully'); - response.json({ success: true, result }); - } catch (error) { - logger.error('Error sending email with attachment from URL:', error); - response.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error) }); - } + const options = { + wordwrap: 130, + }; + const textMessage = convert(message, options); + const fileBuffer = fs.readFileSync(tempFilePath); + const attachmentFilename = fileName || path.basename(fileUrl.split('?')[0]); + + const data = { + from: process.env.MAILGUN_FROM_ADDRESS, + to: toAddress, + subject: subject, + text: textMessage, + html: message, + attachment: { + data: fileBuffer, + filename: attachmentFilename, + contentType: 'application/pdf', + } + }; + + const result = await client.messages.create(process.env.MAILGUN_SERVER, data); + fs.unlinkSync(tempFilePath); + + logger.info('Email with attachment from URL sent successfully'); + response.json({ success: true, result }); + } catch (error) { + logger.error('Error sending email with attachment from URL:', error); + response.status(500).json({ success: false, error: error instanceof Error ? error.message : String(error) }); + } + }); }); export const accessFile = onRequest({ region: '#{SERVICES_RGN}#' }, async (request: Request, response: express.Response) => { - try { - const filePath = request.query.path as string; - if (!filePath) { - response.status(400).send('File path is required'); - return; + return corsHandler(request, response, async () => { + + try { + const filePath = request.query.path as string; + if (!filePath) { + response.status(400).send('File path is required'); + return; + } + + const expirationMs = 60 * 60 * 1000; + + const bucket = getStorage().bucket(); + const file = bucket.file(filePath); + + const [exists] = await file.exists(); + if (!exists) { + response.status(404).send('File not found'); + return; + } + + const [signedUrl] = await file.getSignedUrl({ + action: 'read', + expires: Date.now() + expirationMs, + responseDisposition: `attachment; filename="${path.basename(filePath)}"`, + }); + + response.redirect(signedUrl); + logger.info(`File access redirect for ${filePath}`); + } catch (error) { + logger.error('Error accessing file:', error); + response.status(500).send('Error accessing file'); } - - const expirationMs = 60 * 60 * 1000; - - const bucket = getStorage().bucket(); - const file = bucket.file(filePath); - - const [exists] = await file.exists(); - if (!exists) { - response.status(404).send('File not found'); - return; - } - - const [signedUrl] = await file.getSignedUrl({ - action: 'read', - expires: Date.now() + expirationMs, - responseDisposition: `attachment; filename="${path.basename(filePath)}"`, - }); - - response.redirect(signedUrl); - logger.info(`File access redirect for ${filePath}`); - } catch (error) { - logger.error('Error accessing file:', error); - response.status(500).send('Error accessing file'); - } + }); }); export const sendEmailMessage = onRequest({ region: '#{SERVICES_RGN}#' }, (request: Request, response: express.Response) => { - const mailgun = new Mailgun(formData); - const mailGunClient = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY }); + return corsHandler(request, response, async () => { - const toAddress = request.body.toAddress; - const subject = request.body.subject; - const message = request.body.message; - const options = { - wordwrap: 130, - }; + const mailgun = new Mailgun(formData); + const mailGunClient = mailgun.client({ username: 'api', key: process.env.MAILGUN_API_KEY }); - const textMessage = convert(message, options); - mailGunClient.messages.create(process.env.MAILGUN_SERVER, { - from: process.env.MAILGUN_FROM_ADDRESS, - to: toAddress, - subject: subject, - text: textMessage, - html: message - }).then((res: any) => { - logger.info(res); - response.send(res); - }).catch((err: any) => { - logger.error(err); - response.send(err); + const toAddress = request.body.toAddress; + const subject = request.body.subject; + const message = request.body.message; + const options = { + wordwrap: 130, + }; + + const textMessage = convert(message, options); + mailGunClient.messages.create(process.env.MAILGUN_SERVER, { + from: process.env.MAILGUN_FROM_ADDRESS, + to: toAddress, + subject: subject, + text: textMessage, + html: message + }).then((res: any) => { + logger.info(res); + response.send(res); + }).catch((err: any) => { + logger.error(err); + response.send(err); + }); }); }); export const sendSMSMessage = onRequest({ region: '#{SERVICES_RGN}#' }, (request: Request, response: express.Response) => { - const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN); - const { to, body } = request.body; - client.messages - .create({ - body: body, - from: process.env.TWILIO_PHONE_NUMBER, - to: to - }) - .then((message: any) => { - logger.info('SMS sent successfully:', message.sid); - response.json({ success: true, messageId: message.sid }); - }) - .catch((error: any) => { - logger.error('Error sending SMS:', error); - response.status(500).json({ success: false, error: error.message }); - }); + return corsHandler(request, response, async () => { + + const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN); + const { to, body } = request.body; + client.messages + .create({ + body: body, + from: process.env.TWILIO_PHONE_NUMBER, + to: to + }) + .then((message: any) => { + logger.info('SMS sent successfully:', message.sid); + response.json({ success: true, messageId: message.sid }); + }) + .catch((error: any) => { + logger.error('Error sending SMS:', error); + response.status(500).json({ success: false, error: error.message }); + }); + }); }); interface Invitation { @@ -180,6 +191,7 @@ export const notifyInvitation = onDocumentCreated({ document: 'notifications/{notificationId}', region: '#{SERVICES_RGN}#' }, async (event: any) => { + const invitation = event.data?.data() as Invitation; const invitationId = event.params.invitationId; @@ -343,55 +355,58 @@ export const createCashfreeOrder = onRequest({ export const verifyCashfreePayment = onRequest({ region: '#{SERVICES_RGN}#' }, async (request: Request, response: express.Response) => { - try { - const orderId = request.body.order_id || request.query.order_id; + return corsHandler(request, response, async () => { - if (!orderId) { - response.status(400).json({ error: 'Order ID is required' }); - return; - } + try { + const orderId = request.body.order_id || request.query.order_id; - const clientId = process.env.CASHFREE_CLIENT_ID; - const clientSecret = process.env.CASHFREE_CLIENT_SECRET; - const isTest = process.env.CASHFREE_ENVIRONMENT !== 'production'; - - const apiUrl = isTest - ? `https://sandbox.cashfree.com/pg/orders/${orderId}` - : `https://api.cashfree.com/pg/orders/${orderId}`; - - const cashfreeResponse = await axios.get( - apiUrl, - { - headers: { - 'x-api-version': '2022-09-01', - 'x-client-id': clientId, - 'x-client-secret': clientSecret - } + if (!orderId) { + response.status(400).json({ error: 'Order ID is required' }); + return; } - ); - await admin.firestore().collection('payment_orders').doc(orderId).update({ - orderStatus: cashfreeResponse.data.order_status, - paymentDetails: cashfreeResponse.data, - updatedAt: new Date() - }); + const clientId = process.env.CASHFREE_CLIENT_ID; + const clientSecret = process.env.CASHFREE_CLIENT_SECRET; + const isTest = process.env.CASHFREE_ENVIRONMENT !== 'production'; - if (request.headers['x-webhook-source'] === 'cashfree') { - response.status(200).send('OK'); - return; + const apiUrl = isTest + ? `https://sandbox.cashfree.com/pg/orders/${orderId}` + : `https://api.cashfree.com/pg/orders/${orderId}`; + + const cashfreeResponse = await axios.get( + apiUrl, + { + headers: { + 'x-api-version': '2022-09-01', + 'x-client-id': clientId, + 'x-client-secret': clientSecret + } + } + ); + + await admin.firestore().collection('payment_orders').doc(orderId).update({ + orderStatus: cashfreeResponse.data.order_status, + paymentDetails: cashfreeResponse.data, + updatedAt: new Date() + }); + + if (request.headers['x-webhook-source'] === 'cashfree') { + response.status(200).send('OK'); + return; + } + + response.json({ + status: cashfreeResponse.data.order_status, + paymentDetails: cashfreeResponse.data + }); + + logger.info(`Cashfree payment verified: ${orderId}`); + } catch (error: any) { + logger.error('Cashfree payment verification error:', error); + response.status(500).json({ + error: 'Failed to verify payment status', + details: error.response?.data || error.message + }); } - - response.json({ - status: cashfreeResponse.data.order_status, - paymentDetails: cashfreeResponse.data - }); - - logger.info(`Cashfree payment verified: ${orderId}`); - } catch (error: any) { - logger.error('Cashfree payment verification error:', error); - response.status(500).json({ - error: 'Failed to verify payment status', - details: error.response?.data || error.message - }); - } + }); });