FRE-622: Wire analytics services to tRPC API layer with comprehensive router
Create analytics-router.ts with ~30 tRPC endpoints for KPI management, alert rules, scheduled reports, cohort analysis, and NPS survey integration. Register router in index.ts under 'analytics' namespace. Fix pre-existing bugs in service files: snake_case to camelCase conversion, missing non-null assertions, and incorrect DB access patterns. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -40,7 +40,7 @@ export async function createCohort(
|
||||
};
|
||||
|
||||
const result = await db.insert(cohorts).values(cohort).returning();
|
||||
return result[0];
|
||||
return result[0]!;
|
||||
}
|
||||
|
||||
export async function addCohortMember(
|
||||
@@ -103,7 +103,7 @@ export async function getCohortAnalysis(
|
||||
};
|
||||
|
||||
return {
|
||||
cohort,
|
||||
cohort: cohort!,
|
||||
retention,
|
||||
metrics,
|
||||
};
|
||||
|
||||
@@ -42,7 +42,7 @@ export async function submitNPSResponse(
|
||||
};
|
||||
|
||||
const result = await db.insert(npsResponses).values(response).returning();
|
||||
return result[0];
|
||||
return result[0]!;
|
||||
}
|
||||
|
||||
export async function calculateNPS(
|
||||
@@ -122,7 +122,7 @@ export async function getNPSOverTime(
|
||||
const grouped: Record<string, NPSResponse[]> = {};
|
||||
|
||||
for (const response of responses) {
|
||||
const date = response.created_at;
|
||||
const date = response.createdAt;
|
||||
const key =
|
||||
granularity === "weekly"
|
||||
? getWeekKey(date)
|
||||
@@ -186,7 +186,7 @@ export function generateNPSSurveyInAppPrompt(): { question: string; scale: strin
|
||||
function getWeekKey(date: Date): string {
|
||||
const start = new Date(date);
|
||||
start.setDate(start.getDate() - start.getDay());
|
||||
return start.toISOString().split("T")[0];
|
||||
return start.toISOString().split("T")[0]!;
|
||||
}
|
||||
|
||||
function getMonthKey(date: Date): string {
|
||||
|
||||
@@ -178,7 +178,7 @@ export async function createScheduledReport(
|
||||
lastRunAt: null,
|
||||
nextRunAt: computeNextRun(input.schedule),
|
||||
}).returning();
|
||||
return result[0];
|
||||
return result[0]!;
|
||||
}
|
||||
|
||||
export async function getActiveScheduledReports(
|
||||
|
||||
@@ -134,7 +134,7 @@ export async function sendSlackAlert(
|
||||
type: "header",
|
||||
text: {
|
||||
type: "plain_text",
|
||||
text: `🚨 KPI Alert: ${alert.kpi_key}`,
|
||||
text: `🚨 KPI Alert: ${alert.kpiKey}`,
|
||||
emoji: true,
|
||||
},
|
||||
},
|
||||
@@ -147,7 +147,7 @@ export async function sendSlackAlert(
|
||||
},
|
||||
{
|
||||
type: "mrkdwn",
|
||||
text: `*Current Value:*\n${alert.kpi_value.toFixed(2)}`,
|
||||
text: `*Current Value:*\n${alert.kpiValue.toFixed(2)}`,
|
||||
},
|
||||
{
|
||||
type: "mrkdwn",
|
||||
@@ -155,7 +155,7 @@ export async function sendSlackAlert(
|
||||
},
|
||||
{
|
||||
type: "mrkdwn",
|
||||
text: `*Time:*\n${new Date(alert.created_at?.getTime() ?? Date.now()).toISOString()}`,
|
||||
text: `*Time:*\n${new Date(alert.createdAt.getTime()).toISOString()}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -196,22 +196,14 @@ export async function sendSlackAlert(
|
||||
}
|
||||
|
||||
async function markAlertSent(alertId: number): Promise<void> {
|
||||
const db = await getDb();
|
||||
if (db) {
|
||||
try {
|
||||
const { db } = await import("../../db/config/migrations");
|
||||
await db
|
||||
.update(alerts)
|
||||
.set({ wasSent: true, sentAt: new Date() })
|
||||
.where(eq(alerts.id, alertId));
|
||||
}
|
||||
}
|
||||
|
||||
async function getDb(): Promise<DrizzleDB | undefined> {
|
||||
try {
|
||||
const { createDatabaseManager } = await import("../../db/config/database");
|
||||
const manager = createDatabaseManager();
|
||||
return manager.getDb();
|
||||
} catch {
|
||||
return undefined;
|
||||
console.error("Failed to mark alert as sent:", alertId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user