mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-20 22:03:48 +00:00
refactor(api): remove the deprecated /api/stats endpoint
The only consumer (the marketing landing) now uses /api/v1/stats, so drop the legacy /api/stats route and its handler. Delete src/routes/stats.ts and its test; repoint the index CORS test at /api/v1/stats. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -34,7 +34,6 @@ Single Cloudflare Worker built with Hono. Routes:
|
||||
| ------------------------------------ | ---------------------------------------------------------------------- | ------- |
|
||||
| `GET /` | Public status page (monitoring counters + link to admin) |
|
||||
| `POST /api/inbound` | Webhook from ForwardEmail; IP-allowlisted to their MX sources |
|
||||
| `GET /api/stats` | Deprecated alias of `GET /api/v1/stats` (public monitoring counters) |
|
||||
| `/api/v1/feeds*` | Versioned REST API (Bearer/proxy auth) — feeds + emails CRUD |
|
||||
| `GET /api/v1/stats` | Public monitoring counters (JSON, CORS); canonical stats endpoint |
|
||||
| `GET /api/openapi.json` | OpenAPI 3.1 spec (public) |
|
||||
@@ -65,7 +64,6 @@ src/
|
||||
files.ts # R2 attachment serving
|
||||
hub.ts # WebSub hub
|
||||
home.tsx # Public status page (GET /)
|
||||
stats.ts # Monitoring counters API (GET /api/stats)
|
||||
admin.tsx # Admin UI entrypoint (hono/jsx)
|
||||
admin/ # Admin sub-modules
|
||||
feeds.tsx # Feeds CRUD UI
|
||||
|
||||
+1
-1
@@ -156,7 +156,7 @@ This feature is **optional**. If no R2 bucket is bound, attachments are silently
|
||||
|
||||
Attachments are deleted from R2 automatically when the corresponding email is deleted from the admin UI, or when an email is dropped during feed size trimming.
|
||||
|
||||
**Monitoring storage / free tier:** the status page (`/`) and `/api/stats` report R2 space used (against the **10 GB** R2 free tier) and an estimate of KV space used (against the **1 GB** KV free tier). The figures are refreshed hourly by the cron trigger. KV usage is an estimate based on stored email sizes, so treat it as a lower bound.
|
||||
**Monitoring storage / free tier:** the status page (`/`) and `/api/v1/stats` report R2 space used (against the **10 GB** R2 free tier) and an estimate of KV space used (against the **1 GB** KV free tier). The figures are refreshed hourly by the cron trigger. KV usage is an estimate based on stored email sizes, so treat it as a lower bound.
|
||||
|
||||
### External auth provider (Authelia / Authentik / reverse proxy)
|
||||
|
||||
|
||||
@@ -57,13 +57,11 @@ Main routes:
|
||||
- `src/routes/api/`: versioned REST API + OpenAPI spec/docs (`/api/v1/*`, `/api/openapi.json`, `/api/docs`)
|
||||
- `src/lib/feed-service.ts`: shared feed create/update/delete (used by the admin UI and the REST API)
|
||||
- `src/routes/home.tsx`: public status page (`GET /`)
|
||||
- `src/routes/stats.ts`: monitoring counters API (`GET /api/stats`)
|
||||
|
||||
### Monitoring
|
||||
|
||||
`GET /api/v1/stats` returns JSON counters (public, no auth, CORS-enabled) for
|
||||
uptime/monitoring tools and the landing page. `GET /api/stats` is a deprecated alias kept
|
||||
for backward compatibility — prefer the versioned path. Both expose the same fields:
|
||||
uptime/monitoring tools and the landing page:
|
||||
|
||||
| Field | Meaning |
|
||||
| ----------------------------- | -------------------------------------------------------- |
|
||||
|
||||
+2
-2
@@ -45,9 +45,9 @@ describe("CORS middleware", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("makes /api/stats readable from any origin", async () => {
|
||||
it("makes /api/v1/stats readable from any origin", async () => {
|
||||
const res = await worker.fetch(
|
||||
req("/api/stats", { headers: { Origin: "https://example.com" } }),
|
||||
req("/api/v1/stats", { headers: { Origin: "https://example.com" } }),
|
||||
env as unknown as Env,
|
||||
);
|
||||
expect(res.status).toBe(200);
|
||||
|
||||
+1
-6
@@ -6,7 +6,6 @@ import { handle as handleAtom } from "./routes/atom";
|
||||
import { handle as handleAdmin } from "./routes/admin";
|
||||
import { handle as handleEntry } from "./routes/entries";
|
||||
import { handle as handleFiles } from "./routes/files";
|
||||
import { handle as handleStats } from "./routes/stats";
|
||||
import { handle as handleHome } from "./routes/home";
|
||||
import { handle as handleFavicon, handleFeedFavicon } from "./routes/favicon";
|
||||
import { hubRouter } from "./routes/hub";
|
||||
@@ -148,10 +147,6 @@ api.use("/inbound", async (c, next) => {
|
||||
// API routes (inbound webhook)
|
||||
api.post("/inbound", handleInbound);
|
||||
|
||||
// Public monitoring stats (JSON) — readable from any origin (landing page, embeds)
|
||||
api.use("/stats", cors({ origin: "*" }));
|
||||
api.get("/stats", handleStats);
|
||||
|
||||
// RSS feed routes (public)
|
||||
rss.get("/:feedId", handleRSS);
|
||||
|
||||
@@ -223,7 +218,7 @@ export default {
|
||||
logger.info("Feed TTL cleanup", { deleted: expiredIds.length });
|
||||
}
|
||||
|
||||
// Refresh the cached storage-usage snapshot for the status page / /api/stats.
|
||||
// Refresh the cached storage-usage snapshot for the status page / /api/v1/stats.
|
||||
try {
|
||||
const r2 = attachmentBucket
|
||||
? await scanR2Usage(attachmentBucket)
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
import { describe, it, expect } from "vitest";
|
||||
import worker from "../index";
|
||||
import { createMockEnv } from "../test/setup";
|
||||
import { bumpCounters } from "../utils/stats";
|
||||
import { FEEDS_LIST_KEY } from "../config/constants";
|
||||
import type { Env, StatsResponse } from "../types";
|
||||
|
||||
function req(path: string, init: RequestInit = {}): Request {
|
||||
return new Request(`https://test.getmynews.app${path}`, init);
|
||||
}
|
||||
|
||||
describe("GET /api/stats", () => {
|
||||
it("returns zeroed stats for a fresh instance", async () => {
|
||||
const env = createMockEnv() as unknown as Env;
|
||||
const res = await worker.fetch(req("/api/stats"), env);
|
||||
expect(res.status).toBe(200);
|
||||
const body = (await res.json()) as StatsResponse;
|
||||
expect(body).toMatchObject({
|
||||
feeds_created: 0,
|
||||
feeds_deleted: 0,
|
||||
emails_received: 0,
|
||||
emails_rejected: 0,
|
||||
active_feeds: 0,
|
||||
websub_subscriptions_active: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it("reflects persisted counters and live values", async () => {
|
||||
const env = createMockEnv() as unknown as Env;
|
||||
await env.EMAIL_STORAGE.put(
|
||||
FEEDS_LIST_KEY,
|
||||
JSON.stringify({ feeds: [{ id: "a", title: "A" }] }),
|
||||
);
|
||||
await env.EMAIL_STORAGE.put("websub:a:hash", "{}");
|
||||
await bumpCounters(env.EMAIL_STORAGE, {
|
||||
emails_received: 3,
|
||||
emails_rejected: 1,
|
||||
feeds_created: 1,
|
||||
});
|
||||
|
||||
const res = await worker.fetch(req("/api/stats"), env);
|
||||
const body = (await res.json()) as StatsResponse;
|
||||
expect(body.active_feeds).toBe(1);
|
||||
expect(body.websub_subscriptions_active).toBe(1);
|
||||
expect(body.emails_received).toBe(3);
|
||||
expect(body.emails_rejected).toBe(1);
|
||||
expect(body.feeds_created).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe("GET / (public status page)", () => {
|
||||
it("returns an HTML status page with counters and an admin link", async () => {
|
||||
const env = createMockEnv() as unknown as Env;
|
||||
await bumpCounters(env.EMAIL_STORAGE, { emails_received: 7 });
|
||||
|
||||
const res = await worker.fetch(req("/"), env);
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get("Content-Type")).toContain("text/html");
|
||||
|
||||
const html = await res.text();
|
||||
expect(html).toContain('href="/admin"');
|
||||
expect(html).toContain("Active feeds");
|
||||
expect(html).toContain("Emails received");
|
||||
expect(html).toContain("7");
|
||||
});
|
||||
});
|
||||
@@ -1,7 +0,0 @@
|
||||
import { Context } from "hono";
|
||||
import { Env } from "../types";
|
||||
import { getStats } from "../utils/stats";
|
||||
|
||||
export async function handle(c: Context<{ Bindings: Env }>): Promise<Response> {
|
||||
return c.json(await getStats(c.env));
|
||||
}
|
||||
Reference in New Issue
Block a user