From bf3a4d967216778d554da3ba1b1989a70f78afda Mon Sep 17 00:00:00 2001 From: Young Lee <8462583+yl8976@users.noreply.github.com> Date: Fri, 6 Feb 2026 13:36:17 -0800 Subject: [PATCH] Improve admin delete confirmations --- README.md | 1 + src/routes/admin.test.ts | 111 ++++++++++++ src/routes/admin.ts | 363 +++++++++++++++++++++++++++++++++++---- src/scripts/toast.ts | 41 ++++- src/styles/components.ts | 43 +++++ 5 files changed, 521 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 788e2eb..97c1885 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Email-to-RSS keeps the same workflow while avoiding shared domains and shared da - One-click feed creation from an admin dashboard - Bulk feed/email deletion from the admin dashboard (safe checkbox-based flow) +- Inline double-confirm delete interactions with toast feedback in the admin dashboard - Resizable + sortable table columns in the admin dashboard (Table view) - Unique newsletter addresses per feed (for example `apple.mountain.42@yourdomain.com`) - ForwardEmail webhook ingestion with source-IP verification diff --git a/src/routes/admin.test.ts b/src/routes/admin.test.ts index 65c1f2c..3befc9b 100644 --- a/src/routes/admin.test.ts +++ b/src/routes/admin.test.ts @@ -263,6 +263,42 @@ describe("Admin Routes", () => { expect(feedConfig).toBeNull(); }); + it("should return JSON for feed deletion when requested", async () => { + const authCookie = await loginAndGetCookie(); + const formData = new FormData(); + formData.append("title", "JSON Feed"); + formData.append("description", "Test Description"); + + const createRes = await request("/admin/feeds/create", { + method: "POST", + headers: { + Cookie: authCookie, + }, + body: formData, + }); + + expect(createRes.status).toBe(302); + + const feedList = (await mockEnv.EMAIL_STORAGE.get( + "feeds:list", + "json", + )) as { feeds: Array<{ id: string; title: string }> } | null; + const feedId = feedList?.feeds[0].id as string; + + const deleteRes = await request(`/admin/feeds/${feedId}/delete?view=list`, { + method: "POST", + headers: { + Cookie: authCookie, + Accept: "application/json", + }, + }); + + expect(deleteRes.status).toBe(200); + const payload = await deleteRes.json(); + expect(payload.ok).toBe(true); + expect(payload.feedId).toBe(feedId); + }); + it("should allow bulk feed deletion with valid authentication", async () => { const authCookie = await loginAndGetCookie(); @@ -310,5 +346,80 @@ describe("Admin Routes", () => { expect(feedListAfter?.feeds.length).toBe(0); }); }); + + describe("Email Management", () => { + it("should return JSON for email deletion when requested", async () => { + const authCookie = await loginAndGetCookie(); + const formData = new FormData(); + formData.append("title", "Email Feed"); + formData.append("description", "Test Description"); + + const createRes = await request("/admin/feeds/create", { + method: "POST", + headers: { + Cookie: authCookie, + }, + body: formData, + }); + + expect(createRes.status).toBe(302); + + const feedList = (await mockEnv.EMAIL_STORAGE.get( + "feeds:list", + "json", + )) as { feeds: Array<{ id: string; title: string }> } | null; + const feedId = feedList?.feeds[0].id as string; + const emailKey = `feed:${feedId}:emails:123456`; + + await mockEnv.EMAIL_STORAGE.put( + emailKey, + JSON.stringify({ + subject: "Hello", + from: "sender@example.com", + content: "
Hi
", + receivedAt: 123456, + headers: {}, + }), + ); + + const feedMetadataKey = `feed:${feedId}:metadata`; + const feedMetadata = (await mockEnv.EMAIL_STORAGE.get( + feedMetadataKey, + "json", + )) as { emails: Array<{ key: string; subject: string; receivedAt: number }> } | null; + const updatedMetadata = { + emails: [ + ...(feedMetadata?.emails || []), + { key: emailKey, subject: "Hello", receivedAt: 123456 }, + ], + }; + await mockEnv.EMAIL_STORAGE.put( + feedMetadataKey, + JSON.stringify(updatedMetadata), + ); + + const deleteRes = await request(`/admin/emails/${emailKey}/delete?feedId=${feedId}`, { + method: "POST", + headers: { + Cookie: authCookie, + Accept: "application/json", + }, + }); + + expect(deleteRes.status).toBe(200); + const payload = await deleteRes.json(); + expect(payload.ok).toBe(true); + expect(payload.emailKey).toBe(emailKey); + + const deletedEmail = await mockEnv.EMAIL_STORAGE.get(emailKey, "json"); + expect(deletedEmail).toBeNull(); + + const metadataAfter = (await mockEnv.EMAIL_STORAGE.get( + feedMetadataKey, + "json", + )) as { emails: Array<{ key: string; subject: string; receivedAt: number }> } | null; + expect(metadataAfter?.emails.length).toBe(0); + }); + }); }); }); diff --git a/src/routes/admin.ts b/src/routes/admin.ts index e428000..66681d8 100644 --- a/src/routes/admin.ts +++ b/src/routes/admin.ts @@ -476,14 +476,15 @@ app.get("/", async (c) => { `${clampText(feed.title, 320)} ${feed.id} ${clampText(feed.description || "", 320)}`.toLowerCase(); return html` -