Files
WatchLink/src/lib/room-actions.ts

127 lines
4.0 KiB
TypeScript
Raw Normal View History

2026-05-15 20:13:29 +02:00
"use server";
import { RoomVisibility } from "@prisma/client";
import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";
import { prisma } from "./prisma";
2026-05-15 22:06:33 +02:00
import { requireCurrentUser, userIsAdmin } from "./session";
import { getAppSettings } from "./settings";
2026-05-15 20:13:29 +02:00
function normalizeSlug(value: string) {
return value
.trim()
.toLowerCase()
.replace(/[^a-z0-9_-]+/g, "-")
.replace(/^-+|-+$/g, "")
.slice(0, 48);
}
export async function createRoom(formData: FormData) {
const user = await requireCurrentUser();
const settings = await getAppSettings();
2026-05-15 20:13:29 +02:00
const name = String(formData.get("name") || "").trim();
const visibility = String(formData.get("visibility") || settings.defaultRoomVisibility);
2026-05-15 20:13:29 +02:00
const baseSlug = normalizeSlug(name);
if (!name || !baseSlug) {
redirect("/rooms?error=invalid-room");
}
let slug = baseSlug;
let suffix = 2;
while (await prisma.room.findUnique({ where: { slug }, select: { id: true } })) {
slug = `${baseSlug}-${suffix}`;
suffix += 1;
}
const allowedVisibility: RoomVisibility[] = ["PUBLIC", "FRIENDS", "EXPLICIT", "ROLE_RESTRICTED"];
const roomVisibility = allowedVisibility.includes(visibility as RoomVisibility)
? (visibility as RoomVisibility)
: "FRIENDS";
const room = await prisma.room.create({
data: {
name,
slug,
ownerId: user.id,
visibility: roomVisibility
},
select: { slug: true }
});
revalidatePath("/rooms");
revalidatePath("/dashboard");
redirect(`/rooms/${encodeURIComponent(room.slug)}`);
}
2026-05-15 22:06:33 +02:00
export async function updateRoomSettings(formData: FormData) {
const user = await requireCurrentUser();
const roomId = String(formData.get("roomId") || "");
const name = String(formData.get("name") || "").trim().slice(0, 80);
const visibility = String(formData.get("visibility") || "FRIENDS");
const room = await prisma.room.findUnique({
where: { id: roomId },
include: { members: { where: { userId: user.id }, select: { canManage: true } } }
});
if (!room || !canManageRoom(user, room) || !name) return;
const allowedVisibility: RoomVisibility[] = ["PUBLIC", "FRIENDS", "EXPLICIT", "ROLE_RESTRICTED"];
const roomVisibility = allowedVisibility.includes(visibility as RoomVisibility)
? (visibility as RoomVisibility)
: room.visibility;
await prisma.room.update({
where: { id: room.id },
data: {
name,
visibility: roomVisibility
}
});
revalidateRoom(room.slug);
redirect(`/rooms/${encodeURIComponent(room.slug)}?rail=Settings&saved=1`);
}
export async function addRoomMember(formData: FormData) {
const user = await requireCurrentUser();
const roomId = String(formData.get("roomId") || "");
const username = String(formData.get("username") || "").trim().toLowerCase();
const canManage = formData.get("canManage") === "on";
const room = await prisma.room.findUnique({
where: { id: roomId },
include: { members: { where: { userId: user.id }, select: { canManage: true } } }
});
if (!room || !canManageRoom(user, room) || !username) return;
const invited = await prisma.user.findUnique({ where: { username }, select: { id: true } });
if (!invited || invited.id === room.ownerId) {
redirect(`/rooms/${encodeURIComponent(room.slug)}?rail=Invite&error=user`);
}
await prisma.roomMember.upsert({
where: { roomId_userId: { roomId: room.id, userId: invited.id } },
update: { canManage },
create: { roomId: room.id, userId: invited.id, canManage }
});
revalidateRoom(room.slug);
redirect(`/rooms/${encodeURIComponent(room.slug)}?rail=Invite&saved=1`);
}
function canManageRoom(
user: Awaited<ReturnType<typeof requireCurrentUser>>,
room: { ownerId: string | null; members: Array<{ canManage: boolean }> }
) {
return room.ownerId === user.id || userIsAdmin(user) || room.members.some((member) => member.canManage);
}
function revalidateRoom(slug: string) {
revalidatePath(`/rooms/${encodeURIComponent(slug)}`);
revalidatePath("/rooms");
revalidatePath("/dashboard");
}