mirror of
https://github.com/juherr/kill-the-news.git
synced 2026-06-20 22:03:48 +00:00
feat(api): make /api/v1/stats public and point the landing at it
Unify the monitoring stats on the versioned API: /api/v1/stats is now public (no auth) and CORS-enabled, mirroring the legacy /api/stats. The marketing landing (docs/index.html) now fetches /api/v1/stats; /api/stats is kept as a deprecated alias for existing monitors. Feed/email routes remain token-gated. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -252,10 +252,11 @@ describe("REST API (/api/v1)", () => {
|
||||
});
|
||||
|
||||
describe("Stats", () => {
|
||||
it("returns monitoring counters", async () => {
|
||||
it("returns monitoring counters without a token (public)", async () => {
|
||||
await createFeed();
|
||||
const res = await request("/api/v1/stats", { headers: authHeaders });
|
||||
const res = await request("/api/v1/stats");
|
||||
expect(res.status).toBe(200);
|
||||
expect(res.headers.get("Access-Control-Allow-Origin")).toBe("*");
|
||||
const stats = (await res.json()) as {
|
||||
feeds_created: number;
|
||||
active_feeds: number;
|
||||
@@ -273,12 +274,15 @@ describe("REST API (/api/v1)", () => {
|
||||
expect(res.status).toBe(200);
|
||||
const doc = (await res.json()) as {
|
||||
openapi: string;
|
||||
paths: Record<string, unknown>;
|
||||
paths: Record<string, { get?: { security?: unknown[] } }>;
|
||||
};
|
||||
expect(doc.openapi).toBe("3.1.0");
|
||||
expect(doc.paths).toHaveProperty("/v1/feeds");
|
||||
expect(doc.paths).toHaveProperty("/v1/feeds/{feedId}");
|
||||
expect(doc.paths).toHaveProperty("/v1/stats");
|
||||
// Feed routes are secured; stats is public.
|
||||
expect(doc.paths["/v1/feeds"].get?.security).toBeTruthy();
|
||||
expect(doc.paths["/v1/stats"].get?.security).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { OpenAPIHono, createRoute, z } from "@hono/zod-openapi";
|
||||
import { cors } from "hono/cors";
|
||||
import { Scalar } from "@scalar/hono-api-reference";
|
||||
import { Env, FeedConfig } from "../../types";
|
||||
import { apiAuthMiddleware } from "../../lib/auth";
|
||||
@@ -77,8 +78,12 @@ export const apiApp = new OpenAPIHono<AppEnv>({
|
||||
},
|
||||
});
|
||||
|
||||
// Token auth on every /v1 route. The spec + docs stay public.
|
||||
apiApp.use("/v1/*", apiAuthMiddleware);
|
||||
// Token auth on the feed/email routes. The spec, docs, and /v1/stats stay public.
|
||||
apiApp.use("/v1/feeds", apiAuthMiddleware);
|
||||
apiApp.use("/v1/feeds/*", apiAuthMiddleware);
|
||||
|
||||
// Public monitoring stats — readable from any origin (landing page, embeds).
|
||||
apiApp.use("/v1/stats", cors({ origin: "*" }));
|
||||
|
||||
apiApp.openAPIRegistry.registerComponent("securitySchemes", "bearerAuth", {
|
||||
type: "http",
|
||||
@@ -363,11 +368,9 @@ apiApp.openapi(
|
||||
method: "get",
|
||||
path: "/v1/stats",
|
||||
tags: ["Stats"],
|
||||
summary: "Read monitoring counters",
|
||||
security: bearer,
|
||||
summary: "Read monitoring counters (public)",
|
||||
responses: {
|
||||
200: jsonContent(StatsSchema, "Monitoring counters"),
|
||||
401: jsonContent(ErrorSchema, "Unauthorized"),
|
||||
},
|
||||
}),
|
||||
async (c) => {
|
||||
|
||||
Reference in New Issue
Block a user