Files
elegalsoftware/apps/api/src/routes/tool-usage.ts
T

54 lines
1.8 KiB
TypeScript
Raw Normal View History

2026-04-26 02:42:42 -04:00
import type { FastifyInstance } from 'fastify';
import { z } from 'zod';
import { and, eq, gte, sql } from 'drizzle-orm';
import { getDb, toolUsage } from '@lawdesk/db';
const TOOL_NAMES = [
'hourly-rate-calculator',
'case-profitability',
'billable-hours-tracker',
'document-templates',
] as const;
const logBody = z.object({
tool: z.enum(TOOL_NAMES),
sessionId: z.string().max(64).optional(),
});
export async function toolUsageRoutes(app: FastifyInstance) {
// Log a usage event. Rate-limited per IP so a malicious caller can't pump up "online now" counts.
app.post(
'/api/tool-usage',
{ config: { rateLimit: { max: 60, timeWindow: '1 minute' } } },
async (req, reply) => {
const parsed = logBody.safeParse(req.body);
if (!parsed.success) return reply.code(400).send({ error: 'invalid_tool' });
await getDb().insert(toolUsage).values({
tool: parsed.data.tool,
sessionId: parsed.data.sessionId ?? null,
ip: req.ip ?? null,
});
return { ok: true };
},
);
// Per-tool count of unique sessions in the last 5 minutes — what the public pages display
// as "X online". Public route, very cheap query.
app.get('/api/tool-usage/online', async () => {
const since = new Date(Date.now() - 5 * 60 * 1000);
const rows = await getDb()
.select({
tool: toolUsage.tool,
// Distinct (session_id, ip) so multiple page hits from the same browser don't multi-count
count: sql<number>`count(distinct coalesce(${toolUsage.sessionId}, host(${toolUsage.ip}::inet)))::int`,
})
.from(toolUsage)
.where(gte(toolUsage.createdAt, since))
.groupBy(toolUsage.tool);
const map: Record<string, number> = {};
for (const r of rows) map[r.tool] = r.count;
return { online: map, since: since.toISOString() };
});
}