import type { Metadata } from "next"; import Link from "next/link"; import { notFound } from "next/navigation"; import { ContentCard, EmptyState, StatBlock } from "@/components/public/ui"; import { prisma } from "@/lib/prisma"; export const revalidate = 300; // Arcturus stores room access as a short string on rooms.state. Map each known // value to a friendly label + lock icon; unknown values fall back to the raw // string so we never hide a real (future) state from the visitor. const STATE_LABELS: Record = { open: { label: "Open", icon: "🔓" }, locked: { label: "Locked (doorbell)", icon: "🔒" }, password: { label: "Password", icon: "🔑" }, invisible: { label: "Invisible", icon: "đŸ‘ģ" }, }; function describeState(state: string): { label: string; icon: string } { // eslint-disable-next-line security/detect-object-injection -- STATE_LABELS has known keys, fallback provided return STATE_LABELS[state] ?? { label: state || "Unknown", icon: "đŸšĒ" }; } /** Parse a route id to a positive integer, or null when it isn't numeric. */ function parseRoomId(raw: string): number | null { if (!/^\d+$/.test(raw)) return null; const id = Number(raw); return Number.isSafeInteger(id) && id > 0 ? id : null; } async function getRoom(raw: string) { const id = parseRoomId(raw); if (id === null) return null; return prisma.rooms .findUnique({ where: { id }, select: { id: true, ownerId: true, ownerName: true, name: true, description: true, state: true, users: true, usersMax: true, category: true, score: true, tags: true, }, }) .catch(() => null); } export async function generateMetadata({ params, }: { params: Promise<{ id: string }>; }): Promise { const { id } = await params; const room = await getRoom(id); if (!room) return { title: "Room" }; return { title: room.name || `Room #${room.id}`, description: room.description || `Visit ${room.name || "this room"} in the hotel.`, }; } export default async function RoomPage({ params, }: { params: Promise<{ id: string }>; }) { const { id } = await params; // Guard non-numeric / absent ids before touching the DB. if (parseRoomId(id) === null) notFound(); const room = await getRoom(id); if (!room) notFound(); // Resolve the live owner username by id; fall back to the room's denormalised // owner_name column if the account row is missing or the query degrades. const owner = room.ownerId > 0 ? await prisma.user .findUnique({ where: { id: room.ownerId }, select: { username: true }, }) .catch(() => null) : null; const ownerName = owner?.username || room.ownerName || "Unknown"; const state = describeState(room.state); const tags = room.tags .split(/[,;\s]+/) .map((t) => t.trim()) .filter(Boolean); return (

← Back to the hotel

Owned by{" "} {ownerName} } > {room.description ? (

{room.description}

) : (

This room has no description.

)}
{tags.length > 0 ? (
{tags.map((tag) => ( #{tag} ))}
) : null}
Enter the hotel to visit {room.name || `room #${room.id}`} in 3D.
); }