import { publicReadFailure } from "@/lib/services/public-read"; import "server-only"; import { and, asc, count, eq, inArray, like, or } from "drizzle-orm"; import { db, Rooms, User, WebsiteEvent } from "@/lib/db"; import { loadNewsArchive } from "@/lib/services/news-archive"; function pageNumber(value: number | undefined, total: number) { return Math.min( Math.max(1, Math.ceil(total / 12)), Number.isFinite(value) ? Math.max(1, Math.trunc(value ?? 1)) : 1, ); } export async function loadPublicSearch(options: { query: string; usersPage?: number; roomsPage?: number; newsPage?: number; eventsPage?: number; }) { const query = (typeof options.query === "string" ? options.query : "") .trim() .slice(0, 191); if (!query) return null; const userWhere = like(User.username, `%${query}%`); const roomWhere = and( like(Rooms.name, `%${query}%`), eq(Rooms.state, "open"), ); const eventWhere = and( inArray(WebsiteEvent.status, ["published", "completed"]), or( like(WebsiteEvent.title, `%${query}%`), like(WebsiteEvent.description, `%${query}%`), ), ); const [users, rooms, news, events] = await Promise.allSettled([ (async () => { const [{ value: total }] = await db .select({ value: count() }) .from(User) .where(userWhere); const page = pageNumber(options.usersPage, Number(total)); const rows = await db .select({ id: User.id, username: User.username, look: User.look, online: User.online, }) .from(User) .where(userWhere) .orderBy(asc(User.username), asc(User.id)) .limit(12) .offset((page - 1) * 12); return { rows, total: Number(total), page, lastPage: Math.max(1, Math.ceil(Number(total) / 12)), }; })(), (async () => { const [{ value: total }] = await db .select({ value: count() }) .from(Rooms) .where(roomWhere); const page = pageNumber(options.roomsPage, Number(total)); const rows = await db .select({ id: Rooms.id, name: Rooms.name, ownerName: Rooms.ownerName, users: Rooms.users, usersMax: Rooms.usersMax, }) .from(Rooms) .where(roomWhere) .orderBy(asc(Rooms.name), asc(Rooms.id)) .limit(12) .offset((page - 1) * 12); return { rows, total: Number(total), page, lastPage: Math.max(1, Math.ceil(Number(total) / 12)), }; })(), loadNewsArchive({ search: query, page: options.newsPage }), (async () => { const [{ value: total }] = await db .select({ value: count() }) .from(WebsiteEvent) .where(eventWhere); const page = pageNumber(options.eventsPage, Number(total)); const rows = await db .select({ id: WebsiteEvent.id, title: WebsiteEvent.title, startsAt: WebsiteEvent.startsAt, }) .from(WebsiteEvent) .where(eventWhere) .orderBy(asc(WebsiteEvent.startsAt), asc(WebsiteEvent.id)) .limit(12) .offset((page - 1) * 12); return { rows, total: Number(total), page, lastPage: Math.max(1, Math.ceil(Number(total) / 12)), }; })(), ]); for (const [section, result] of Object.entries({ users, rooms, news, events, })) { if (result.status === "rejected") publicReadFailure(`search.${section}`)(result.reason); } return { query, users, rooms, news, events }; }