import { unlink } from "node:fs/promises"; import { prisma } from "@streamclipper/db"; import { env } from "./env"; const CHECK_INTERVAL_MS = 60 * 60 * 1000; // hourly const TTL_SETTING_KEY = "raw_segment_ttl_hours"; async function getTtlHours(): Promise { const setting = await prisma.appSetting.findUnique({ where: { key: TTL_SETTING_KEY } }); const parsed = setting ? Number(setting.value) : NaN; return Number.isFinite(parsed) && parsed > 0 ? parsed : env.rawSegmentTtlHours; } /** * PRD's auto-purge lifecycle: processed raw recordings get deleted 24h after * capture. Only segments that are done being analyzed (PROCESSED/DISCARDED) * and have no candidate still waiting on STT are eligible — deleting a raw * file mid-analysis or mid-transcription would break the pipeline for it. */ async function cleanupOnce(): Promise { const ttlHours = await getTtlHours(); const cutoff = new Date(Date.now() - ttlHours * 60 * 60 * 1000); const staleSegments = await prisma.rawSegment.findMany({ where: { createdAt: { lt: cutoff }, status: { in: ["PROCESSED", "DISCARDED"] }, candidates: { none: { status: "PENDING_STT" } }, }, }); for (const segment of staleSegments) { await prisma.candidateSegment.deleteMany({ where: { rawSegmentId: segment.id } }); await prisma.rawSegment.delete({ where: { id: segment.id } }); await unlink(segment.filePath).catch(() => {}); } if (staleSegments.length > 0) { console.log(`[cleanup] removed ${staleSegments.length} raw segment(s) older than ${ttlHours}h`); } } export function startCleanupLoop(): NodeJS.Timeout { console.log(`[cleanup] checking every ${CHECK_INTERVAL_MS}ms, default TTL ${env.rawSegmentTtlHours}h`); cleanupOnce().catch((err) => console.error("[cleanup] initial run failed:", err)); return setInterval(() => { cleanupOnce().catch((err) => console.error("[cleanup] run failed:", err)); }, CHECK_INTERVAL_MS); }