From 2f234bf097c0089bca31ea78364474be1b008ac9 Mon Sep 17 00:00:00 2001 From: Kieran Klukas <92754843+kcoderhtml@users.noreply.github.com> Date: Tue, 7 May 2024 16:40:20 -0400 Subject: [PATCH 1/4] feat: Add dynamic unsubscribe links to email messages This commit adds dynamic unsubscribe links to the email messages in the `dashboard.astro` file. The unsubscribe links are generated based on the message type and include the appropriate URL for unsubscribing. This enhancement improves the user experience by providing an easy way for recipients to unsubscribe from future emails. --- src/pages/dashboard.astro | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pages/dashboard.astro b/src/pages/dashboard.astro index eaa64da..4499d72 100644 --- a/src/pages/dashboard.astro +++ b/src/pages/dashboard.astro @@ -27,17 +27,17 @@ if (Astro.request.method === "POST") { markdown: messageType === "text/markdown" ? message + - ` \n\n If you want to unsubscribe click [here](${Astro.url.origin}/api/unsubscribe)` + ` \n\n You're receiving this email because you subscribed to ${import.meta.env.NAME}'s newsletter. If you'd like to stop receiving emails from ${import.meta.env.NAME}, [unsubscribe here](${Astro.url.origin}/api/unsubscribe)` : undefined, html: messageType === "text/html" ? message + - ` \n\n If you want to unsubscribe click here ` + ` \n\n You're receiving this email because you subscribed to ${import.meta.env.NAME}'s newsletter. If you'd like to stop receiving emails from ${import.meta.env.NAME}, unsubscribe here ` : undefined, text: messageType === "text/plain" ? message + - ` \n\n If you want to unsubscribe please go to: ${Astro.url.origin}/api/unsubscribe to unsubscribe.` + ` \n\n You're receiving this email because you subscribed to ${import.meta.env.NAME}'s newsletter. If you'd like to stop receiving emails from ${import.meta.env.NAME} go to the following link: ${Astro.url.origin}/api/unsubscribe to unsubscribe.` : undefined, }; From 89c636b0d148c0858e70eda7f2b1c9c1fb362e95 Mon Sep 17 00:00:00 2001 From: Kieran Klukas <92754843+kcoderhtml@users.noreply.github.com> Date: Tue, 7 May 2024 16:41:08 -0400 Subject: [PATCH 2/4] chore: Fix typo in unsubscribe message --- src/pages/api/unsubscribe.astro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/api/unsubscribe.astro b/src/pages/api/unsubscribe.astro index 5cae30f..0c1294f 100644 --- a/src/pages/api/unsubscribe.astro +++ b/src/pages/api/unsubscribe.astro @@ -162,7 +162,7 @@ if (paramEmail && token) { !paramEmail && !token && !unsubscribed && (

- We realy hate to make this a two step process, but we need to make + We really hate to make this a two step process, but we need to make sure you're really you. Please enter your email address below and we'll send you a link to unsubscribe.

From 081f7cb0b7a7c1222a383cae93e47bf2ca70d9e8 Mon Sep 17 00:00:00 2001 From: Kieran Klukas <92754843+kcoderhtml@users.noreply.github.com> Date: Tue, 7 May 2024 16:49:05 -0400 Subject: [PATCH 3/4] feat: Add user removal in dashboard.astro --- src/pages/dashboard.astro | 128 +++++++++++++++++++++++++------------- 1 file changed, 84 insertions(+), 44 deletions(-) diff --git a/src/pages/dashboard.astro b/src/pages/dashboard.astro index 4499d72..6ed3a3c 100644 --- a/src/pages/dashboard.astro +++ b/src/pages/dashboard.astro @@ -1,9 +1,11 @@ --- +import { date } from "astro/zod"; import Layout from "../layouts/Layout.astro"; import { db, Subscribers } from "astro:db"; +import { like } from "astro:db"; -const subscribers = await db.select().from(Subscribers); +let subscribers = await db.select().from(Subscribers); let PageMessage: { ok: boolean; @@ -17,50 +19,74 @@ if (Astro.request.method === "POST") { const message = data.get("message"); const messageType = data.get("messageType"); - const emails = subscribers.map((subscriber) => subscriber.email); - - const email = { - to: import.meta.env.SEND_EMAIL, - bcc: emails, - from: import.meta.env.SEND_EMAIL, - subject: subject, - markdown: - messageType === "text/markdown" - ? message + - ` \n\n You're receiving this email because you subscribed to ${import.meta.env.NAME}'s newsletter. If you'd like to stop receiving emails from ${import.meta.env.NAME}, [unsubscribe here](${Astro.url.origin}/api/unsubscribe)` - : undefined, - html: - messageType === "text/html" - ? message + - ` \n\n You're receiving this email because you subscribed to ${import.meta.env.NAME}'s newsletter. If you'd like to stop receiving emails from ${import.meta.env.NAME}, unsubscribe here ` - : undefined, - text: - messageType === "text/plain" - ? message + - ` \n\n You're receiving this email because you subscribed to ${import.meta.env.NAME}'s newsletter. If you'd like to stop receiving emails from ${import.meta.env.NAME} go to the following link: ${Astro.url.origin}/api/unsubscribe to unsubscribe.` - : undefined, - }; - - const response = await fetch(import.meta.env.EMAIL_API, { - method: "POST", - headers: { - "Content-Type": "application/json", - Authorization: process.env.EMAIL_API_KEY || "", - }, - body: JSON.stringify(email), - }); - - if (!response.ok) { - PageMessage = { - ok: false, - message: `Failed to send message to users. ${response.status} ${response.statusText}`, - }; - console.log(response.status, response.statusText, await response.text()); - } else { - PageMessage = { - ok: true, - message: "Email sent successfully!", + if (data.has("send")) { + const emails = subscribers.map((subscriber) => subscriber.email); + + const email = { + to: import.meta.env.SEND_EMAIL, + bcc: emails, + from: import.meta.env.SEND_EMAIL, + subject: subject, + markdown: + messageType === "text/markdown" + ? message + + ` \n\n You're receiving this email because you subscribed to ${import.meta.env.NAME}'s newsletter. If you'd like to stop receiving emails from ${import.meta.env.NAME}, [unsubscribe here](${Astro.url.origin}/api/unsubscribe)` + : undefined, + html: + messageType === "text/html" + ? message + + ` \n\n You're receiving this email because you subscribed to ${import.meta.env.NAME}'s newsletter. If you'd like to stop receiving emails from ${import.meta.env.NAME}, unsubscribe here ` + : undefined, + text: + messageType === "text/plain" + ? message + + ` \n\n You're receiving this email because you subscribed to ${import.meta.env.NAME}'s newsletter. If you'd like to stop receiving emails from ${import.meta.env.NAME} go to the following link: ${Astro.url.origin}/api/unsubscribe to unsubscribe.` + : undefined, }; + + const response = await fetch(import.meta.env.EMAIL_API, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: process.env.EMAIL_API_KEY || "", + }, + body: JSON.stringify(email), + }); + + if (!response.ok) { + PageMessage = { + ok: false, + message: `Failed to send message to users. ${response.status} ${response.statusText}`, + }; + console.log(response.status, response.statusText, await response.text()); + } else { + PageMessage = { + ok: true, + message: "Email sent successfully!", + }; + } + } else if (data.has("remove")) { + const email = data.get("email"); + + const response = await db + .delete(Subscribers) + .where(like(Subscribers.email, email as string)); + + subscribers = subscribers.filter( + (subscriber) => subscriber.email !== email + ); + + if (response) { + PageMessage = { + ok: true, + message: "User removed", + }; + } else { + PageMessage = { + ok: false, + message: "Failed to remove user", + }; + } } } --- @@ -115,6 +141,7 @@ if (Astro.request.method === "POST") { @@ -127,6 +154,7 @@ if (Astro.request.method === "POST") { Name Email Subscribed At + Remove user @@ -147,6 +175,18 @@ if (Astro.request.method === "POST") { minute: "2-digit", })} + +
+ + +
+ )) } From d0ece7433202a30d680008c2daa42c6084d4d117 Mon Sep 17 00:00:00 2001 From: Kieran Klukas <92754843+kcoderhtml@users.noreply.github.com> Date: Tue, 7 May 2024 16:51:22 -0400 Subject: [PATCH 4/4] feat: Add email subscription check before saving This commit adds a check to ensure that the email being subscribed is not already present in the database. If the email is already subscribed, an error is thrown. This enhancement improves the data integrity and prevents duplicate email subscriptions. --- src/pages/api/subscribe.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/pages/api/subscribe.ts b/src/pages/api/subscribe.ts index 182d181..2e115f5 100644 --- a/src/pages/api/subscribe.ts +++ b/src/pages/api/subscribe.ts @@ -1,5 +1,5 @@ import type { APIRoute } from 'astro'; -import { db, Subscribers } from 'astro:db'; +import { db, like, Subscribers } from 'astro:db'; import arcjet, { validateEmail } from "@arcjet/node"; import type { ArcjetNodeRequest } from "@arcjet/node"; @@ -40,8 +40,6 @@ export const POST: APIRoute = async ({ params, request }) => { email: json.email, }); - console.log("Arcjet decision", decision); - if (decision.isDenied()) { return new Response(JSON.stringify({ ok: false, reason: decision.reason }), { status: 400, @@ -50,6 +48,13 @@ export const POST: APIRoute = async ({ params, request }) => { } }); } else { + // check if the email is already subscribed + const existing = await db.select().from(Subscribers).where(like(Subscribers.email, json.email)); + + if (existing.length > 0) { + throw new Error("Email already subscribed"); + } + // Save the email to the database await db.insert(Subscribers).values({ name: json.name,