refactor(analyitics): finished analyitics as a base
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import { and, count, countDistinct, gte, lt, sql } from "drizzle-orm";
|
||||
import { db } from "../db/db.controller.js";
|
||||
import { analytics } from "../db/schema/analytics.schema.js";
|
||||
import { analyticsDaily } from "../db/schema/dailyAnalytics.schema.js";
|
||||
|
||||
export const ignoredRoutePrefixes = [
|
||||
"/health",
|
||||
@@ -29,3 +31,111 @@ type CreateRouteHitInput = {
|
||||
export async function createRouteHit(input: CreateRouteHitInput) {
|
||||
await db.insert(analytics).values(input);
|
||||
}
|
||||
|
||||
function getPreviousBusinessDayWindow(date = new Date()) {
|
||||
const end = new Date(date);
|
||||
end.setHours(7, 0, 0, 0);
|
||||
|
||||
const start = new Date(end);
|
||||
start.setDate(start.getDate() - 1);
|
||||
|
||||
const businessDate = start.toISOString().slice(0, 10);
|
||||
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
businessDate,
|
||||
};
|
||||
}
|
||||
|
||||
export async function runRouteHitAnalyticsCron(): Promise<void> {
|
||||
const result = await aggregateRouteHitsForBusinessDay();
|
||||
|
||||
await cleanupOldRouteHits();
|
||||
|
||||
console.log("Route hit analytics aggregated", result);
|
||||
}
|
||||
|
||||
export async function aggregateRouteHitsForBusinessDay() {
|
||||
const { start, end, businessDate } = getPreviousBusinessDayWindow();
|
||||
|
||||
const rows = await db
|
||||
.select({
|
||||
businessDate: sql<string>`${businessDate}`,
|
||||
method: analytics.method,
|
||||
routePattern: analytics.routePattern,
|
||||
module: sql<string>`COALESCE(${analytics.module}, 'unknown')`,
|
||||
|
||||
totalHits: count(),
|
||||
uniqueUsers: countDistinct(analytics.userId),
|
||||
|
||||
successCount: sql<number>`
|
||||
COUNT(*) FILTER (WHERE ${analytics.statusCode} < 400)
|
||||
`,
|
||||
errorCount: sql<number>`
|
||||
COUNT(*) FILTER (WHERE ${analytics.statusCode} >= 400)
|
||||
`,
|
||||
|
||||
avgDurationMs: sql<number>`
|
||||
COALESCE(ROUND(AVG(${analytics.durationMs})), 0)
|
||||
`,
|
||||
maxDurationMs: sql<number>`
|
||||
COALESCE(MAX(${analytics.durationMs}), 0)
|
||||
`,
|
||||
|
||||
firstHitAt: sql<Date>`
|
||||
COALESCE(MIN(${analytics.createdAt}), NOW())
|
||||
`,
|
||||
lastHitAt: sql<Date>`
|
||||
COALESCE(MAX(${analytics.createdAt}), NOW())
|
||||
`,
|
||||
})
|
||||
.from(analytics)
|
||||
.where(and(gte(analytics.createdAt, start), lt(analytics.createdAt, end)))
|
||||
.groupBy(
|
||||
analytics.method,
|
||||
analytics.routePattern,
|
||||
sql`COALESCE(${analytics.module}, 'unknown')`,
|
||||
);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return {
|
||||
businessDate,
|
||||
inserted: 0,
|
||||
};
|
||||
}
|
||||
|
||||
await db
|
||||
.insert(analyticsDaily)
|
||||
.values(rows)
|
||||
.onConflictDoUpdate({
|
||||
target: [
|
||||
analyticsDaily.businessDate,
|
||||
analyticsDaily.method,
|
||||
analyticsDaily.routePattern,
|
||||
analyticsDaily.module,
|
||||
],
|
||||
set: {
|
||||
totalHits: sql`excluded.total_hits`,
|
||||
uniqueUsers: sql`excluded.unique_users`,
|
||||
successCount: sql`excluded.success_count`,
|
||||
errorCount: sql`excluded.error_count`,
|
||||
avgDurationMs: sql`excluded.avg_duration_ms`,
|
||||
maxDurationMs: sql`excluded.max_duration_ms`,
|
||||
firstHitAt: sql`excluded.first_hit_at`,
|
||||
lastHitAt: sql`excluded.last_hit_at`,
|
||||
updatedAt: sql`now()`,
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
businessDate,
|
||||
inserted: rows.length,
|
||||
};
|
||||
}
|
||||
|
||||
export async function cleanupOldRouteHits() {
|
||||
await db
|
||||
.delete(analytics)
|
||||
.where(lt(analytics.createdAt, sql`now() - interval '4 days'`));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user