214 lines
6.0 KiB
TypeScript
214 lines
6.0 KiB
TypeScript
"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<ReturnType<typeof requireCurrentUser>>
|
|
) {
|
|
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<ReturnType<typeof requireCurrentUser>>
|
|
) {
|
|
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");
|
|
}
|