feat(web): public mesh stats counter + /api/public/stats endpoint
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

Live social-proof counter on the landing page, tied to the E2E
narrative. Formatted as understated mono footer, not hero brag.

Backend — new GET /api/public/stats (unauthed, 60s in-memory cache):
  {
    messagesRouted: SELECT COUNT(*) FROM mesh.message_queue,
    meshesCreated: SELECT COUNT(*) FROM mesh.mesh WHERE archivedAt IS NULL,
    peersActive: SELECT COUNT(*) FROM mesh.presence WHERE disconnectedAt IS NULL,
    lastUpdated: ISO timestamp,
  }

Aggregate counts only — no ids, no names, no ciphertext, no routing
metadata. Safe for public consumption. cache-control header sets
public/s-maxage=60 for edge caching. `x-cache: HIT|MISS` for debug.

Frontend — new MeshStats Server Component at
modules/marketing/home/mesh-stats.tsx. Reads the endpoint server-side
via the ~/lib/api/server client, renders monospace footer:

  ciphertext routed → 4,217 messages · 12 meshes · 8 peers online
  broker sees none of it

Graceful zero state: when messagesRouted === 0 shows
"ciphertext → ready to route" instead of embarrassing zeros. Tabular-
nums for the numeric spans so they don't jitter across renders.

Mounted between <CallToAction /> and <LatestNewsToaster />. Page-level
`export const revalidate = 60` so Next.js ISR refreshes the counter
every minute without a DB hit on every request (combined with the
API cache = two-layer 60s TTL, DB sees ~1 query/minute).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-04-05 16:00:00 +01:00
parent d0dfce6e33
commit 509af3afe0
5 changed files with 150 additions and 0 deletions

View File

@@ -15,6 +15,7 @@ import { authRouter } from "./modules/auth/router";
import { billingRouter } from "./modules/billing/router";
import { myRouter } from "./modules/mesh/router";
import { organizationRouter } from "./modules/organization/router";
import { publicRouter } from "./modules/public/router";
import { storageRouter } from "./modules/storage/router";
import { onError } from "./utils/on-error";
@@ -51,6 +52,7 @@ const appRouter = new Hono()
.route("/billing", billingRouter)
.route("/my", myRouter)
.route("/organizations", organizationRouter)
.route("/public", publicRouter)
.route("/storage", storageRouter)
.onError(onError);

View File

@@ -0,0 +1,57 @@
import { Hono } from "hono";
import { count, isNull } from "@turbostarter/db";
import { mesh, messageQueue, presence } from "@turbostarter/db/schema";
import { db } from "@turbostarter/db/server";
/**
* Unauthed public stats for the landing page counter.
*
* In-memory 60s cache. Results are aggregate counts only — no ids,
* no names, no ciphertext, no routing metadata. Safe for public consumption.
*/
const CACHE_TTL_MS = 60_000;
interface PublicStats {
messagesRouted: number;
meshesCreated: number;
peersActive: number;
lastUpdated: string;
}
let cachedStats: { value: PublicStats; expiresAt: number } | null = null;
const fetchStats = async (): Promise<PublicStats> => {
const [[messagesRouted], [meshesCreated], [peersActive]] = await Promise.all([
db.select({ c: count() }).from(messageQueue),
db
.select({ c: count() })
.from(mesh)
.where(isNull(mesh.archivedAt)),
db
.select({ c: count() })
.from(presence)
.where(isNull(presence.disconnectedAt)),
]);
return {
messagesRouted: messagesRouted?.c ?? 0,
meshesCreated: meshesCreated?.c ?? 0,
peersActive: peersActive?.c ?? 0,
lastUpdated: new Date().toISOString(),
};
};
export const publicRouter = new Hono().get("/stats", async (c) => {
const now = Date.now();
if (cachedStats && cachedStats.expiresAt > now) {
c.header("x-cache", "HIT");
return c.json(cachedStats.value);
}
const value = await fetchStats();
cachedStats = { value, expiresAt: now + CACHE_TTL_MS };
c.header("x-cache", "MISS");
c.header("cache-control", "public, max-age=60, s-maxage=60");
return c.json(value);
});

View File

@@ -186,6 +186,18 @@ export type GetMyMeshStreamResponse = z.infer<
typeof getMyMeshStreamResponseSchema
>;
// ---------------------------------------------------------------------
// Public stats (unauthed landing counter)
// ---------------------------------------------------------------------
export const publicStatsResponseSchema = z.object({
messagesRouted: z.number(),
meshesCreated: z.number(),
peersActive: z.number(),
lastUpdated: z.string(),
});
export type PublicStatsResponse = z.infer<typeof publicStatsResponseSchema>;
export const getMyInvitesResponseSchema = z.object({
sent: z.array(
z.object({