CI / test (push) Successful in 1m10s
Moderators (up to 20 IMVU accounts, set by ID or name) and the owner account boot and ban over realtime (room.boot); bans are listed and lifted over REST. Private rooms are unlisted and joined by the owner, moderators and holders of an accepted invite (room.invite / room.invited / room.invite_reply / room.invite_answer). Room images (PNG/JPEG by magic bytes, up to 512 KB) are stored in the database and served with cache headers. Per-person favorites, ratings and recent rooms. room.watch pushes room.occupancy; participants carry their IMVU name, and GET .../participants lists them. Contract in docs/hosted-rooms.md, "Phase 2". Co-Authored-By: Claude Opus 5.5 <[email protected]>
107 lines
3.7 KiB
TypeScript
107 lines
3.7 KiB
TypeScript
"use server";
|
|
import { revalidatePath } from "next/cache";
|
|
import { redirect } from "next/navigation";
|
|
import { api, unwrap } from "@/lib/api";
|
|
import { attempt, field, type ActionState } from "@/lib/actions";
|
|
|
|
const rating = (form: FormData) => (field(form, "rating") === "adult" ? ("adult" as const) : ("general" as const));
|
|
const privacy = (form: FormData) => (field(form, "privacy") === "private" ? ("private" as const) : ("public" as const));
|
|
const moderators = (form: FormData) =>
|
|
field(form, "moderators")
|
|
.split(/[,\n]/)
|
|
.map((s) => s.trim())
|
|
.filter(Boolean);
|
|
|
|
/** Makes a hosted room, then goes back to the list. */
|
|
export async function createRoom(_: ActionState, form: FormData): Promise<ActionState> {
|
|
const state = await attempt(async () => {
|
|
unwrap(
|
|
await api["hosted-rooms"].post({
|
|
name: field(form, "name"),
|
|
description: field(form, "description"),
|
|
roomPid: Number(field(form, "roomPid")),
|
|
rating: rating(form),
|
|
maxUsers: Number(field(form, "maxUsers")),
|
|
imvuId: Number(field(form, "imvuId")),
|
|
privacy: privacy(form),
|
|
}),
|
|
);
|
|
revalidatePath("/rooms");
|
|
});
|
|
if (state?.error) return state;
|
|
redirect("/rooms");
|
|
}
|
|
|
|
/** The owner's changes to a room. */
|
|
export async function saveRoom(_: ActionState, form: FormData): Promise<ActionState> {
|
|
const n = field(form, "n");
|
|
const state = await attempt(async () => {
|
|
unwrap(
|
|
await api["hosted-rooms"]({ n }).patch({
|
|
name: field(form, "name"),
|
|
description: field(form, "description"),
|
|
roomPid: Number(field(form, "roomPid")),
|
|
rating: rating(form),
|
|
maxUsers: Number(field(form, "maxUsers")),
|
|
privacy: privacy(form),
|
|
moderators: moderators(form),
|
|
}),
|
|
);
|
|
revalidatePath("/rooms");
|
|
});
|
|
if (state?.error) return state;
|
|
redirect("/rooms");
|
|
}
|
|
|
|
/** The room's image: a PNG or JPEG, up to 512 KB. */
|
|
export async function saveImage(_: ActionState, form: FormData): Promise<ActionState> {
|
|
const n = field(form, "n");
|
|
const image = form.get("image");
|
|
if (!(image instanceof File) || image.size === 0) return { error: "Choose an image." };
|
|
if (image.size > 512 * 1024) return { error: "An image is up to 512 KB." };
|
|
return attempt(async () => {
|
|
unwrap(await api["hosted-rooms"]({ n }).image.put({ image }));
|
|
revalidatePath("/rooms");
|
|
revalidatePath(`/rooms/${n}/edit`);
|
|
return "Image saved.";
|
|
});
|
|
}
|
|
|
|
export async function removeImage(_: ActionState, form: FormData): Promise<ActionState> {
|
|
const n = field(form, "n");
|
|
return attempt(async () => {
|
|
unwrap(await api["hosted-rooms"]({ n }).image.delete());
|
|
revalidatePath("/rooms");
|
|
revalidatePath(`/rooms/${n}/edit`);
|
|
});
|
|
}
|
|
|
|
/** Stars a room, or (favorite=0) unstars it. */
|
|
export async function setFavorite(_: ActionState, form: FormData): Promise<ActionState> {
|
|
const n = field(form, "n");
|
|
return attempt(async () => {
|
|
const room = api["hosted-rooms"]({ n }).favorite;
|
|
unwrap(await (field(form, "favorite") === "1" ? room.put() : room.delete()));
|
|
revalidatePath("/rooms");
|
|
});
|
|
}
|
|
|
|
export async function rateRoom(_: ActionState, form: FormData): Promise<ActionState> {
|
|
const n = field(form, "n");
|
|
return attempt(async () => {
|
|
unwrap(await api["hosted-rooms"]({ n }).rating.put({ score: Number(field(form, "score")) }));
|
|
revalidatePath("/rooms");
|
|
});
|
|
}
|
|
|
|
/** Closes a room: everyone in it is sent out. */
|
|
export async function closeRoom(_: ActionState, form: FormData): Promise<ActionState> {
|
|
const n = field(form, "n");
|
|
const state = await attempt(async () => {
|
|
unwrap(await api["hosted-rooms"]({ n }).delete());
|
|
revalidatePath("/rooms");
|
|
});
|
|
if (state?.error) return state;
|
|
redirect("/rooms");
|
|
}
|