From 7b0d8f39e67b0217932277de5e852cffa8ab7ba0 Mon Sep 17 00:00:00 2001 From: Alexander Alemayhu Date: Thu, 8 Aug 2024 19:20:34 +0200 Subject: [PATCH] feat: add endpoint for contacting support (#1570) This is a work in progress. --- .../IndexController/IndexController.ts | 19 +++++++ src/lib/misc/getUploadLimits.ts | 5 -- src/routes/DefaultRouter.ts | 10 ++++ src/services/EmailService/EmailService.ts | 54 +++++++++++++++++++ 4 files changed, 83 insertions(+), 5 deletions(-) diff --git a/src/controllers/IndexController/IndexController.ts b/src/controllers/IndexController/IndexController.ts index 950cba1aa..e393a440e 100644 --- a/src/controllers/IndexController/IndexController.ts +++ b/src/controllers/IndexController/IndexController.ts @@ -6,6 +6,7 @@ import AuthenticationService from '../../services/AuthenticationService'; import TokenRepository from '../../data_layer/TokenRepository'; import UsersRepository from '../../data_layer/UsersRepository'; import { configureUserLocal } from '../../routes/middleware/configureUserLocal'; +import { useDefaultEmailService } from '../../services/EmailService/EmailService'; class IndexController { public getIndex(request: express.Request, response: express.Response) { @@ -18,6 +19,24 @@ class IndexController { response.send(getIndexFileContents()); }); } + + async contactUs(req: express.Request, res: express.Response) { + const { name, email, message } = req.body; + console.info('Contact Us', name, email, message); + if (!email || !message) { + return res.status(400).send({ error: 'Missing email or message' }); + } + + const attachments = req.files as Express.Multer.File[]; + const defaultEmailService = useDefaultEmailService(); + await defaultEmailService.sendContactEmail( + name, + email, + message, + attachments + ); + return res.status(200).send(); + } } export default IndexController; diff --git a/src/lib/misc/getUploadLimits.ts b/src/lib/misc/getUploadLimits.ts index 8a8ac7e0a..5dcc33816 100644 --- a/src/lib/misc/getUploadLimits.ts +++ b/src/lib/misc/getUploadLimits.ts @@ -4,11 +4,6 @@ const FREE_USER_MAX_UPLOAD_SIZE = 100 * 1024 * 1024; const PAYING_MAX_FIELD_SIZE = FREE_USER_MAX_FIELD_SIZE * 10; const PAYING_MAX_UPLOAD_SIZE = FREE_USER_MAX_UPLOAD_SIZE * 100; -export interface UploaderInfo { - patron: boolean; - subscriber: boolean; -} - export const getUploadLimits = (paying: boolean) => { if (paying) { return { diff --git a/src/routes/DefaultRouter.ts b/src/routes/DefaultRouter.ts index e9600fb80..50a4730e1 100644 --- a/src/routes/DefaultRouter.ts +++ b/src/routes/DefaultRouter.ts @@ -1,7 +1,13 @@ import express from 'express'; +import multer from 'multer'; + import RequireAuthentication from './middleware/RequireAuthentication'; import IndexController from '../controllers/IndexController/IndexController'; +const upload = multer({ + limits: { fileSize: 25 * 1024 * 1024 }, +}); + const DefaultRouter = () => { const controller = new IndexController(); const router = express.Router(); @@ -10,6 +16,10 @@ const DefaultRouter = () => { router.get('/search*', RequireAuthentication, controller.getIndex); router.get('*', (req, res) => controller.getIndex(req, res)); + router.post('/api/contact-us', upload.array('attachments'), (req, res) => + controller.contactUs(req, res) + ); + return router; }; diff --git a/src/services/EmailService/EmailService.ts b/src/services/EmailService/EmailService.ts index c0b5dc83c..74838123a 100644 --- a/src/services/EmailService/EmailService.ts +++ b/src/services/EmailService/EmailService.ts @@ -8,11 +8,20 @@ import { } from './constants'; import { isValidDeckName, addDeckNameSuffix } from '../../lib/anki/format'; import { ClientResponse } from '@sendgrid/mail'; +import { SUPPORT_EMAIL_ADDRESS } from '../../lib/constants'; + +type EmailResponse = { didSend: boolean; error?: Error }; export interface IEmailService { sendResetEmail(email: string, token: string): void; sendConversionEmail(email: string, filename: string, contents: Buffer): void; sendConversionLinkEmail(email: string, filename: string, link: string): void; + sendContactEmail( + name: string, + email: string, + message: string, + attachments: Express.Multer.File[] + ): Promise; } class EmailService implements IEmailService { @@ -79,6 +88,35 @@ class EmailService implements IEmailService { await sgMail.send(msg); } + + async sendContactEmail( + name: string, + email: string, + message: string, + attachments: Express.Multer.File[] + ): Promise { + const msg = { + to: SUPPORT_EMAIL_ADDRESS, + from: DEFAULT_SENDER, + subject: `Contact form submission on behalf of ${ + name ?? 'Anon' + } <${email}>`, + text: `Message: ${message}\n\n`, + attachments: attachments.map((file) => ({ + content: file.buffer.toString('base64'), + filename: file.originalname, + type: file.mimetype, + disposition: 'attachment', + })), + }; + try { + await sgMail.send(msg); + return { didSend: true }; + } catch (e) { + console.error('Error sending email', e); + return { didSend: false, error: e as Error }; + } + } } export class UnimplementedEmailService implements IEmailService { @@ -93,6 +131,22 @@ export class UnimplementedEmailService implements IEmailService { sendConversionLinkEmail(email: string, filename: string, link: string): void { console.info('sendConversionLinkEmail not handled', email, filename, link); } + + sendContactEmail( + name: string, + email: string, + message: string, + attachments: Express.Multer.File[] + ): Promise { + console.info( + 'sendContactEmail not handled', + name, + email, + message, + attachments + ); + return Promise.resolve({ didSend: false }); + } } export const useDefaultEmailService = () => {