"use server"; import { unlink } from "node:fs/promises"; import { prisma } from "@streamclipper/db"; import { revalidatePath } from "next/cache"; import { redirect } from "next/navigation"; import { postToApiDaemon } from "../lib/apiDaemon"; export async function addChannel(formData: FormData) { const name = String(formData.get("name") ?? "").trim(); const youtubeHandle = String(formData.get("youtubeHandle") ?? "").trim(); const channelId = String(formData.get("channelId") ?? "").trim(); if (!name || !youtubeHandle || !channelId) { throw new Error("Kanal adı, handle ve channel_id alanları zorunlu."); } await prisma.channel.create({ data: { name, youtubeHandle, channelId, isActive: true }, }); revalidatePath("/"); } /** ShortVideo has a required FK to CandidateSegment (candidate_id) — every deletion path that removes candidates must clear their shorts (row + rendered file) first, or Postgres rejects the candidate delete. */ async function deleteShortsForCandidates(candidateIds: string[]): Promise { if (candidateIds.length === 0) return; const shorts = await prisma.shortVideo.findMany({ where: { candidateId: { in: candidateIds } } }); await prisma.shortVideo.deleteMany({ where: { candidateId: { in: candidateIds } } }); await Promise.all(shorts.filter((s) => s.filePath).map((s) => unlink(s.filePath!).catch(() => {}))); } export async function deleteRawSegment(segmentId: string) { const segment = await prisma.rawSegment.findUnique({ where: { id: segmentId }, include: { candidates: true }, }); if (!segment) return; await deleteShortsForCandidates(segment.candidates.map((c) => c.id)); await prisma.candidateSegment.deleteMany({ where: { rawSegmentId: segmentId } }); await prisma.rawSegment.delete({ where: { id: segmentId } }); await unlink(segment.filePath).catch(() => {}); revalidatePath("/segments"); revalidatePath("/", "layout"); } export async function deleteCandidateSegment(candidateId: string) { await deleteShortsForCandidates([candidateId]); await prisma.candidateSegment.delete({ where: { id: candidateId } }); revalidatePath("/segments"); revalidatePath("/", "layout"); } export async function deleteChannel(channelId: string) { const sessions = await prisma.streamSession.findMany({ where: { channelId }, include: { segments: { include: { candidates: true } } }, }); const rawSegmentIds = sessions.flatMap((s) => s.segments.map((seg) => seg.id)); const candidateIds = sessions.flatMap((s) => s.segments.flatMap((seg) => seg.candidates.map((c) => c.id))); const filePaths = sessions.flatMap((s) => s.segments.map((seg) => seg.filePath)); await deleteShortsForCandidates(candidateIds); await prisma.candidateSegment.deleteMany({ where: { rawSegmentId: { in: rawSegmentIds } } }); await prisma.rawSegment.deleteMany({ where: { sessionId: { in: sessions.map((s) => s.id) } } }); await prisma.streamSession.deleteMany({ where: { channelId } }); await prisma.channel.delete({ where: { id: channelId } }); await Promise.all(filePaths.map((p) => unlink(p).catch(() => {}))); revalidatePath("/", "layout"); redirect("/"); } export async function toggleChannelActive(channelId: string, nextActive: boolean) { await prisma.channel.update({ where: { id: channelId }, data: { isActive: nextActive } }); revalidatePath("/", "layout"); } export async function updateChannelSegmentTime(channelId: string, formData: FormData) { const raw = String(formData.get("segmentTimeMin") ?? "").trim(); const minutes = raw === "" ? null : Number(raw); const seconds = minutes !== null && Number.isFinite(minutes) && minutes > 0 ? Math.round(minutes * 60) : null; await prisma.channel.update({ where: { id: channelId }, data: { segmentTimeSec: seconds } }); revalidatePath(`/channels/${channelId}`); } export async function toggleAllChannels(nextActive: boolean) { await prisma.channel.updateMany({ data: { isActive: nextActive } }); revalidatePath("/", "layout"); } export async function stopSession(sessionId: string) { await postToApiDaemon(`/sessions/${sessionId}/stop`); revalidatePath("/", "layout"); } export async function checkChannelNow(channelId: string) { await postToApiDaemon(`/channels/${channelId}/check-now`); revalidatePath("/", "layout"); } export async function forceStartCapture(channelId: string, formData: FormData) { const url = String(formData.get("url") ?? "").trim(); if (!url) throw new Error("YouTube URL zorunlu."); await postToApiDaemon(`/channels/${channelId}/force-start`, { url }); revalidatePath("/", "layout"); } export async function retryRender(candidateId: string) { await postToApiDaemon(`/shorts/${candidateId}/render`); revalidatePath("/segments"); revalidatePath("/", "layout"); } export async function cancelRender(candidateId: string) { await postToApiDaemon(`/shorts/${candidateId}/cancel`); revalidatePath("/segments"); revalidatePath("/", "layout"); } export async function updateSystemSettings(formData: FormData) { const pollSeconds = Number(formData.get("pollIntervalSec")); const ttlHours = Number(formData.get("ttlHours")); if (Number.isFinite(pollSeconds) && pollSeconds >= 5) { await prisma.appSetting.upsert({ where: { key: "poll_interval_ms" }, create: { key: "poll_interval_ms", value: String(pollSeconds * 1000) }, update: { value: String(pollSeconds * 1000) }, }); } if (Number.isFinite(ttlHours) && ttlHours > 0) { await prisma.appSetting.upsert({ where: { key: "raw_segment_ttl_hours" }, create: { key: "raw_segment_ttl_hours", value: String(ttlHours) }, update: { value: String(ttlHours) }, }); } revalidatePath("/settings"); } export async function updateSttEnabled(formData: FormData) { const enabled = formData.get("sttEnabled") === "true"; await prisma.appSetting.upsert({ where: { key: "stt_enabled" }, create: { key: "stt_enabled", value: String(enabled) }, update: { value: String(enabled) }, }); revalidatePath("/settings"); } export async function updateAutoRenderEnabled(formData: FormData) { const enabled = formData.get("autoRenderEnabled") === "true"; await prisma.appSetting.upsert({ where: { key: "auto_render_enabled" }, create: { key: "auto_render_enabled", value: String(enabled) }, update: { value: String(enabled) }, }); revalidatePath("/settings"); } export async function updateDeleteAfterTelegramSend(formData: FormData) { const enabled = formData.get("deleteAfterTelegramSend") === "true"; await prisma.appSetting.upsert({ where: { key: "delete_after_telegram_send" }, create: { key: "delete_after_telegram_send", value: String(enabled) }, update: { value: String(enabled) }, }); revalidatePath("/settings"); } export async function updateNotificationPrefs(formData: FormData) { const streamStart = formData.get("notifyStreamStart") === "true"; const renderDone = formData.get("notifyRenderDone") === "true"; const pollError = formData.get("notifyPollError") === "true"; await prisma.appSetting.upsert({ where: { key: "notify_poll_error" }, create: { key: "notify_poll_error", value: String(pollError) }, update: { value: String(pollError) }, }); await prisma.appSetting.upsert({ where: { key: "notify_stream_start" }, create: { key: "notify_stream_start", value: String(streamStart) }, update: { value: String(streamStart) }, }); await prisma.appSetting.upsert({ where: { key: "notify_render_done" }, create: { key: "notify_render_done", value: String(renderDone) }, update: { value: String(renderDone) }, }); revalidatePath("/settings"); } export async function updateYtdlpCookies(formData: FormData) { const value = String(formData.get("cookies") ?? "").trim(); if (!value) { throw new Error("Cookie içeriği boş olamaz."); } await prisma.appSetting.upsert({ where: { key: "ytdlp_cookies" }, create: { key: "ytdlp_cookies", value }, update: { value }, }); revalidatePath("/settings"); }