import { sql, eq, and, ne } from "drizzle-orm"; import { db } from "@/lib/db/client"; import { playSessions, sessionDevices, sessionEvents, devices, recordings } from "@/lib/db/schema"; export async function getSessionsSummary() { const [totals] = await db .select({ count: sql`count(*)`, totalDurationMs: sql`coalesce(sum(${playSessions.durationMs}), 0)`, avgDurationMs: sql`coalesce(avg(${playSessions.durationMs}), 0)`, }) .from(playSessions) .where(eq(playSessions.status, "completed")); const byKind = await db .select({ kind: playSessions.kind, count: sql`count(*)`, totalDurationMs: sql`coalesce(sum(${playSessions.durationMs}), 0)`, }) .from(playSessions) .where(eq(playSessions.status, "completed")) .groupBy(playSessions.kind); const durationPerDevice = await db .select({ deviceId: devices.id, displayName: devices.displayName, bleName: devices.bleName, totalActiveMs: sql`coalesce(sum(coalesce(${sessionDevices.disconnectedAt}, ${sessionDevices.connectedAt}) - ${sessionDevices.connectedAt}), 0)`, }) .from(sessionDevices) .innerJoin(devices, eq(sessionDevices.deviceId, devices.id)) .groupBy(devices.id); return { ...totals, byKind, durationPerDevice }; } export async function getSessionTimeline(playSessionId: number, bucketMs = 1000) { // Reuse the same expression object (not a `sql`bucket`` alias reference) // in groupBy/orderBy - drizzle doesn't emit a literal `AS bucket` that // SQLite's GROUP BY/ORDER BY could resolve a bare "bucket" identifier // against, so referencing it by name causes "no such column: bucket". const bucket = sql`(${sessionEvents.tsMs} / ${bucketMs}) * ${bucketMs}`; return db .select({ bucket, sessionDeviceId: sessionEvents.sessionDeviceId, slotLabel: sessionDevices.slotLabel, avgValue: sql`avg(${sessionEvents.value})`, maxValue: sql`max(${sessionEvents.value})`, }) .from(sessionEvents) .innerJoin(sessionDevices, eq(sessionEvents.sessionDeviceId, sessionDevices.id)) .where(and(eq(sessionEvents.playSessionId, playSessionId), ne(sessionEvents.commandType, "stop"))) .groupBy(bucket, sessionEvents.sessionDeviceId) .orderBy(bucket); } export async function getDeviceUsageStats() { return db .select({ deviceId: devices.id, displayName: devices.displayName, bleName: devices.bleName, sessionCount: sql`count(distinct ${sessionDevices.playSessionId})`, totalActiveMs: sql`coalesce(sum(coalesce(${sessionDevices.disconnectedAt}, ${sessionDevices.connectedAt}) - ${sessionDevices.connectedAt}), 0)`, lastUsedAt: sql`max(${sessionDevices.connectedAt})`, }) .from(devices) .leftJoin(sessionDevices, eq(sessionDevices.deviceId, devices.id)) .groupBy(devices.id); } export async function getDeviceCommandCounts() { return db .select({ deviceId: devices.id, commandCount: sql`count(${sessionEvents.id})`, }) .from(devices) .leftJoin(sessionDevices, eq(sessionDevices.deviceId, devices.id)) .leftJoin(sessionEvents, eq(sessionEvents.sessionDeviceId, sessionDevices.id)) .groupBy(devices.id); } export async function getRecordingLibraryStats() { const [totals] = await db .select({ count: sql`count(*)`, avgDurationMs: sql`coalesce(avg(${recordings.durationMs}), 0)`, totalPlayCount: sql`coalesce(sum(${recordings.playCount}), 0)`, }) .from(recordings); const list = await db .select({ id: recordings.id, name: recordings.name, durationMs: recordings.durationMs, playCount: recordings.playCount, lastPlayedAt: recordings.lastPlayedAt, }) .from(recordings) .orderBy(sql`${recordings.playCount} desc`); return { ...totals, list }; }