Initial commit — eLegal Software monorepo
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
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() };
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user