Files
claudemesh/packages/api/src/modules/admin/router.ts
Alejandro Gutiérrez 30928cd71d feat(api): admin backoffice router — meshes, sessions, invites, audit
Extends the Hono adminRouter with four new read-only mesh admin modules:
meshes, sessions, invites, audit. Each ships {queries,router}.ts following
the existing users/organizations/customers pattern (paginated Drizzle
transactions, getOrderByFromSort sorting, ilike search, enum filters).

- GET /admin/meshes — paginated list with owner join + member count subquery
- GET /admin/meshes/:id — detail: members, presences, invites, last 50 audit
  events (returns {mesh: null,...} shell on not-found to stay single-shape
  for Hono RPC inference)
- GET /admin/sessions — live WS presences across every mesh, joined to
  member/mesh for display, status + active/disconnected filters
- GET /admin/invites — invite tokens w/ mesh + createdBy user joins,
  revoked/expired filters
- GET /admin/audit — mesh audit log with eventType/meshId/date filters

Summary endpoint extended: new GET /admin/summary/mesh returns
{meshes, activeMeshes, totalPresences, activePresences, messages24h}.
Messages24h derived from audit_log where event_type='message_sent'
in the past 24h.

Schemas live in packages/api/src/schema/mesh-admin.ts, re-exported from
the schema barrel. All mesh/role/transport enums mirror the DB enums
from packages/db/src/schema/mesh.ts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 22:47:27 +01:00

62 lines
1.8 KiB
TypeScript

import { Hono } from "hono";
import { enforceAdmin, enforceAuth } from "../../middleware";
import { auditRouter } from "./audit/router";
import { getMessages24hCount } from "./audit/queries";
import { getCustomersCount } from "./customers/queries";
import { customersRouter } from "./customers/router";
import { invitesRouter } from "./invites/router";
import {
getActiveMeshesCount,
getMeshesCount,
} from "./meshes/queries";
import { meshesRouter } from "./meshes/router";
import { getOrganizationsCount } from "./organizations/queries";
import { organizationsRouter } from "./organizations/router";
import {
getActivePresencesCount,
getPresencesCount,
} from "./sessions/queries";
import { sessionsRouter } from "./sessions/router";
import { getUsersCount } from "./users/queries";
import { usersRouter } from "./users/router";
export const adminRouter = new Hono()
.use(enforceAuth)
.use(enforceAdmin)
.route("/users", usersRouter)
.route("/organizations", organizationsRouter)
.route("/customers", customersRouter)
.route("/meshes", meshesRouter)
.route("/sessions", sessionsRouter)
.route("/invites", invitesRouter)
.route("/audit", auditRouter)
.get("/summary", async (c) => {
const [users, organizations, customers] = await Promise.all([
getUsersCount(),
getOrganizationsCount(),
getCustomersCount(),
]);
return c.json({ users, organizations, customers });
})
.get("/summary/mesh", async (c) => {
const [meshes, activeMeshes, totalPresences, activePresences, messages24h] =
await Promise.all([
getMeshesCount(),
getActiveMeshesCount(),
getPresencesCount(),
getActivePresencesCount(),
getMessages24hCount(),
]);
return c.json({
meshes,
activeMeshes,
totalPresences,
activePresences,
messages24h,
});
});