Files
screenclipper/apps/frontend/app/actions.ts
T
ayrisdevandClaude Sonnet 5 ee79dce340 fix: Ayarlar sayfasındaki switch'ler kapatılıp kaydedilince kalıcı olmuyordu
Radix Switch'in kendi gizli-input form entegrasyonu bu ortamda Server
Action'ın FormData'sına güvenilir şekilde ulaşmıyordu — switch'i "off"
yapıp Kaydet'e basınca eski değer kalıcı oluyordu (STT hep otomatik
çalışmaya devam ediyordu). Artık her switch kendi değerini doğrudan
kontrol eden bir hidden input'la (SettingSwitch) submit ediliyor,
Radix'in iç mekanizmasına bağımlı değil.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-02 15:10:24 +03:00

182 lines
6.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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<void> {
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 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 updateNotificationPrefs(formData: FormData) {
const streamStart = formData.get("notifyStreamStart") === "true";
const renderDone = formData.get("notifyRenderDone") === "true";
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");
}