From 79457d86f2ffa351d0cfa5d915240c3698148510 Mon Sep 17 00:00:00 2001 From: Denis Vlasov Date: Fri, 27 Sep 2024 00:28:41 +0300 Subject: [PATCH 1/3] show email attachments --- app.js | 21 +++++++++++++++++++- test/app.test.js | 50 +++++++++++++++++++++++++++++++++++------------- views/email.pug | 13 +++++++++---- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/app.js b/app.js index 47d05de..367ae1c 100644 --- a/app.js +++ b/app.js @@ -55,14 +55,33 @@ app.get("/emails/:id", async (req, res, next) => { try { const messages = await fetchMessages(); const message = messages[req.params.id]; - const email = await createEmail(message); + const attachments = email.attachments.filter((attachment) => attachment.contentDisposition === "attachment"); res.render("email", { subject: email.subject, to: email.to, htmlContent: email.html, + attachments, + messageId: req.params.id, + }); + } catch (err) { + next(err); + } +}); + +app.get("/emails/:id/attachments/:attachmentId", async (req, res, next) => { + try { + const messages = await fetchMessages(); + const message = messages[req.params.id]; + const email = await createEmail(message); + const attachment = email.attachments.find((attachment) => attachment.partId === req.params.attachmentId); + + res.set({ + "Content-Type": attachment.contentType, + "Content-Disposition": `inline; filename="${attachment.filename}"`, }); + res.send(attachment.content); } catch (err) { next(err); } diff --git a/test/app.test.js b/test/app.test.js index c047334..6921c7b 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -4,22 +4,43 @@ import app from "../app.js"; const generateMockEml = () => { const timestamp = new Date().toUTCString(); - return `Date: ${timestamp}\r\nFrom: sender@example.com\r\nTo: recipient@example.com\r\nSubject: Test Email\r\n\r\nThis is the body of the email.`; + return `Date: Fri, 27 Sep 2024 00:18:19 +0300 +From: sender@example.com +To: recipient@example.com +Subject: Test Email +Content-Type: multipart/related; + boundary=b510f43f668889c35c6ed92270c106a8dc55a157b9add4fffd76983ad5cc + +--b510f43f668889c35c6ed92270c106a8dc55a157b9add4fffd76983ad5cc +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; charset=UTF-8 + +email body + +--b510f43f668889c35c6ed92270c106a8dc55a157b9add4fffd76983ad5cc +Content-Disposition: attachment; filename="file.pdf" +Content-ID: +Content-Transfer-Encoding: quoted-printable +Content-Type: image/pdf; name="file.pdf" + +attachment-content +--b510f43f668889c35c6ed92270c106a8dc55a157b9add4fffd76983ad5cc-- +`; }; describe("App Tests", () => { beforeEach(() => { - nock(`${process.env.LOCALSTACK_HOST || 'http://localhost:4566'}`) + nock(`${process.env.LOCALSTACK_HOST || "http://localhost:4566"}`) .get("/_aws/ses") .reply(200, { messages: [ { Timestamp: Date.now(), - RawData: generateMockEml(), + RawData: generateMockEml() }, { Timestamp: Date.now(), - RawData: generateMockEml(), + RawData: generateMockEml() }, { Timestamp: Date.now(), @@ -29,8 +50,8 @@ describe("App Tests", () => { }, Body: { text_part: null, - html_part: "This is a test email with html", - }, + html_part: "This is a test email with html" + } }, { Timestamp: Date.now(), @@ -40,10 +61,10 @@ describe("App Tests", () => { }, Body: { text_part: "This is a test email", - html_part: null, - }, - }, - ], + html_part: null + } + } + ] }); }); @@ -89,13 +110,16 @@ describe("App Tests", () => { test("GET /emails/:id/download should return status 200 and download the email as a file", async () => { const response = await supertest(app).get("/emails/0/download"); expect(response.status).toBe(200); - expect(response.header["content-disposition"]).toBe( - 'attachment; filename="Test Email.eml"', - ); + expect(response.header["content-disposition"]).toBe('attachment; filename="Test Email.eml"'); }); test("GET /emails/:id/download should return status 400 when the email does not contain raw data", async () => { const response = await supertest(app).get("/emails/2/download"); expect(response.status).toBe(400); }); + + test("GET /emails/:id/attachments/:attachmentId should return status 200 and show attachment", async () => { + const response = await supertest(app).get("/emails/1/attachments/2"); + expect(response.status).toBe(200); + }); }); diff --git a/views/email.pug b/views/email.pug index 9df6e63..462fa61 100644 --- a/views/email.pug +++ b/views/email.pug @@ -13,14 +13,19 @@ html padding: 10px 16px; font-weight: normal; } - + .subject-value { font-weight: bold; } body .container - | Subject line: + | Subject line:  span.subject-value #{subject}
- | To: - span.subject-value #{to} + | To:  + span.subject-value #{to}
+ if attachments.length > 0 + | Attachments:  + each att in attachments + a(href='/emails/' + messageId + '/attachments/' + att.partId)= att.filename + =" " | !{htmlContent} From 611ea7591240c71aaf5d252340819803a85ae7d7 Mon Sep 17 00:00:00 2001 From: Denis Vlasov Date: Fri, 27 Sep 2024 18:35:05 +0300 Subject: [PATCH 2/3] update link styles --- views/email.pug | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/views/email.pug b/views/email.pug index 462fa61..d658bee 100644 --- a/views/email.pug +++ b/views/email.pug @@ -17,6 +17,11 @@ html .subject-value { font-weight: bold; } + + .attachment-link { + color: #fff; + font-weight: bold; + } body .container | Subject line:  @@ -26,6 +31,6 @@ html if attachments.length > 0 | Attachments:  each att in attachments - a(href='/emails/' + messageId + '/attachments/' + att.partId)= att.filename + a.attachment-link(href='/emails/' + messageId + '/attachments/' + att.partId, target="_blank")= att.filename =" " | !{htmlContent} From d575804b13457ab6a9f6b7344e646b9e47981136 Mon Sep 17 00:00:00 2001 From: Denis Vlasov Date: Wed, 2 Oct 2024 17:29:26 +0300 Subject: [PATCH 3/3] remove nbsp --- views/email.pug | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/views/email.pug b/views/email.pug index d658bee..be1e172 100644 --- a/views/email.pug +++ b/views/email.pug @@ -24,12 +24,12 @@ html } body .container - | Subject line:  + ="Subject line: " span.subject-value #{subject}
- | To:  + ="To: " span.subject-value #{to}
if attachments.length > 0 - | Attachments:  + ="Attachments: " each att in attachments a.attachment-link(href='/emails/' + messageId + '/attachments/' + att.partId, target="_blank")= att.filename =" "