Yayın durdurma, kanal duraklat/devam (tekli+toplu), şimdi kontrol et, manuel URL ile zorla kayıt başlatma, canlı player+log önizleme, oturum süre sayacı, başarısız/takılı render için tekrar dene ve iptal, poll aralığı + ham segment TTL'yi panelden canlı değiştirme, cookie bayatlık uyarısı, Telegram bildirim aç/kapa, ve kullanım/maliyet özeti (/stats). api-daemon'ın yeni state-değiştiren endpoint'leri (stop/force-start/ render/cancel) INTERNAL_API_TOKEN ile korunuyor — bu daemon'ın portu Coolify'de public'e açık olduğu için korumasız bırakmak güvenlik açığı olurdu. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
50 lines
1.9 KiB
TypeScript
50 lines
1.9 KiB
TypeScript
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<number> {
|
|
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<void> {
|
|
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);
|
|
}
|