"use server"; import { revalidatePath } from "next/cache"; import { normalizeMediaUrl } from "./media"; import { prisma } from "./prisma"; import { requireCurrentUser, userIsAdmin } from "./session"; import { getAppSettings } from "./settings"; export async function addMediaToRoom(formData: FormData) { const user = await requireCurrentUser(); const roomId = String(formData.get("roomId") || ""); const sourceUrl = String(formData.get("sourceUrl") || "").trim(); if (!roomId || !sourceUrl) return; const room = await prisma.room.findFirst({ where: { id: roomId, OR: [{ ownerId: user.id }, { members: { some: { userId: user.id } } }, { visibility: "PUBLIC" }] }, select: { id: true, slug: true } }); if (!room) return; const settings = await getAppSettings(); const media = normalizeMediaUrl(sourceUrl); if (!settings.allowedProviders.includes(media.provider)) return; const nextPosition = await prisma.mediaSource.count({ where: { roomId: room.id } }); const created = await prisma.mediaSource.create({ data: { roomId: room.id, submitterId: user.id, provider: media.provider, originalUrl: media.originalUrl, playbackUrl: media.playbackUrl, thumbnailUrl: media.thumbnailUrl, queuePosition: nextPosition + 1, title: media.originalUrl } }); await prisma.room.update({ where: { id: room.id }, data: { currentState: { provider: media.provider, originalUrl: media.originalUrl, playbackUrl: media.playbackUrl, mediaSourceId: created.id, status: "PAUSED", position: 0, rate: 1, updatedBy: user.username, updatedAt: Date.now() } } }); revalidatePath(`/rooms/${encodeURIComponent(room.slug)}`); revalidatePath("/dashboard"); } export async function removeMediaFromRoom(formData: FormData) { const { room, media } = await requireMediaManager(formData); if (!room || !media) return; await prisma.mediaSource.delete({ where: { id: media.id } }); await normalizeQueue(room.id); revalidateRoom(room.slug); } export async function moveMediaUp(formData: FormData) { await moveMedia(formData, -1); } export async function moveMediaDown(formData: FormData) { await moveMedia(formData, 1); } export async function setCurrentMedia(formData: FormData) { const user = await requireCurrentUser(); const mediaId = String(formData.get("mediaId") || ""); if (!mediaId) return; const media = await prisma.mediaSource.findUnique({ where: { id: mediaId }, include: { room: { select: { id: true, slug: true, ownerId: true, visibility: true, members: { where: { userId: user.id }, select: { canManage: true } } } } } }); if (!media || !canUseRoom(user.id, media.room, user)) return; await prisma.room.update({ where: { id: media.roomId }, data: { currentState: { provider: media.provider, originalUrl: media.originalUrl, playbackUrl: media.playbackUrl, mediaSourceId: media.id, status: "PLAYING", position: 0, rate: 1, updatedBy: user.username, updatedAt: Date.now() } } }); revalidateRoom(media.room.slug); } async function moveMedia(formData: FormData, direction: -1 | 1) { const { room, media } = await requireMediaManager(formData); if (!room || !media) return; const queue = await prisma.mediaSource.findMany({ where: { roomId: room.id }, orderBy: [{ queuePosition: "asc" }, { createdAt: "asc" }, { id: "asc" }], select: { id: true } }); const index = queue.findIndex((item) => item.id === media.id); const target = index + direction; if (index < 0 || target < 0 || target >= queue.length) return; const reordered = [...queue]; [reordered[index], reordered[target]] = [reordered[target], reordered[index]]; await prisma.$transaction( reordered.map((item, itemIndex) => prisma.mediaSource.update({ where: { id: item.id }, data: { queuePosition: itemIndex + 1 } }) ) ); revalidateRoom(room.slug); } async function requireMediaManager(formData: FormData) { const user = await requireCurrentUser(); const mediaId = String(formData.get("mediaId") || ""); if (!mediaId) return { room: null, media: null }; const media = await prisma.mediaSource.findUnique({ where: { id: mediaId }, include: { room: { select: { id: true, slug: true, ownerId: true, visibility: true, members: { where: { userId: user.id }, select: { canManage: true } } } } } }); if (!media || !canManageRoom(user.id, media.room, user)) return { room: null, media: null }; return { room: media.room, media }; } function canUseRoom( userId: string, room: { ownerId: string | null; visibility: string; members: Array<{ canManage: boolean }> }, user?: Awaited> ) { return room.ownerId === userId || Boolean(user && userIsAdmin(user)) || room.visibility === "PUBLIC" || room.members.length > 0; } function canManageRoom( userId: string, room: { ownerId: string | null; members: Array<{ canManage: boolean }> }, user?: Awaited> ) { return room.ownerId === userId || Boolean(user && userIsAdmin(user)) || room.members.some((member) => member.canManage); } async function normalizeQueue(roomId: string) { const queue = await prisma.mediaSource.findMany({ where: { roomId }, orderBy: [{ queuePosition: "asc" }, { createdAt: "asc" }, { id: "asc" }], select: { id: true } }); if (queue.length === 0) return; await prisma.$transaction( queue.map((item, index) => prisma.mediaSource.update({ where: { id: item.id }, data: { queuePosition: index + 1 } }) ) ); } function revalidateRoom(slug: string) { revalidatePath(`/rooms/${encodeURIComponent(slug)}`); revalidatePath("/dashboard"); revalidatePath("/rooms"); }