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>
This commit is contained in:
89
packages/api/src/modules/admin/audit/queries.ts
Normal file
89
packages/api/src/modules/admin/audit/queries.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import {
|
||||
and,
|
||||
between,
|
||||
count,
|
||||
desc,
|
||||
eq,
|
||||
getOrderByFromSort,
|
||||
gte,
|
||||
ilike,
|
||||
inArray,
|
||||
or,
|
||||
sql,
|
||||
} from "@turbostarter/db";
|
||||
import { auditLog, mesh } from "@turbostarter/db/schema";
|
||||
import { db } from "@turbostarter/db/server";
|
||||
|
||||
import type { GetAuditInput } from "../../../schema";
|
||||
|
||||
export const getMessages24hCount = async () =>
|
||||
db
|
||||
.select({ count: count() })
|
||||
.from(auditLog)
|
||||
.where(
|
||||
and(
|
||||
eq(auditLog.eventType, "message_sent"),
|
||||
gte(auditLog.createdAt, dayjs().subtract(24, "hour").toDate()),
|
||||
),
|
||||
)
|
||||
.then((res) => res[0]?.count ?? 0);
|
||||
|
||||
export const getAudit = async (input: GetAuditInput) => {
|
||||
const offset = (input.page - 1) * input.perPage;
|
||||
|
||||
const where = and(
|
||||
input.q
|
||||
? or(
|
||||
ilike(auditLog.eventType, `%${input.q}%`),
|
||||
ilike(mesh.name, `%${input.q}%`),
|
||||
ilike(auditLog.actorPeerId, `%${input.q}%`),
|
||||
)
|
||||
: undefined,
|
||||
input.eventType ? inArray(auditLog.eventType, input.eventType) : undefined,
|
||||
input.meshId ? inArray(auditLog.meshId, input.meshId) : undefined,
|
||||
input.createdAt
|
||||
? between(
|
||||
auditLog.createdAt,
|
||||
dayjs(input.createdAt[0]).startOf("day").toDate(),
|
||||
dayjs(input.createdAt[1]).endOf("day").toDate(),
|
||||
)
|
||||
: undefined,
|
||||
);
|
||||
|
||||
const orderBy = input.sort
|
||||
? getOrderByFromSort({ sort: input.sort, defaultSchema: auditLog })
|
||||
: [desc(auditLog.createdAt)];
|
||||
|
||||
return db.transaction(async (tx) => {
|
||||
const data = await tx
|
||||
.select({
|
||||
id: auditLog.id,
|
||||
meshId: auditLog.meshId,
|
||||
meshName: mesh.name,
|
||||
meshSlug: mesh.slug,
|
||||
eventType: auditLog.eventType,
|
||||
actorPeerId: auditLog.actorPeerId,
|
||||
targetPeerId: auditLog.targetPeerId,
|
||||
metadata: sql<Record<string, unknown>>`${auditLog.metadata}`,
|
||||
createdAt: auditLog.createdAt,
|
||||
})
|
||||
.from(auditLog)
|
||||
.leftJoin(mesh, eq(auditLog.meshId, mesh.id))
|
||||
.where(where)
|
||||
.limit(input.perPage)
|
||||
.offset(offset)
|
||||
.orderBy(...orderBy);
|
||||
|
||||
const total = await tx
|
||||
.select({ count: count() })
|
||||
.from(auditLog)
|
||||
.leftJoin(mesh, eq(auditLog.meshId, mesh.id))
|
||||
.where(where)
|
||||
.execute()
|
||||
.then((res) => res[0]?.count ?? 0);
|
||||
|
||||
return { data, total };
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user