From 416c31643b7af9038c1afcc509cbeb66c4708bfd Mon Sep 17 00:00:00 2001 From: openhands Date: Tue, 25 Aug 2026 21:52:18 +0200 Subject: [PATCH 01/14] perf: optimize dashboard database queries and remove unused imports --- scripts/sync-nitro-urls.cjs | 96 +++++++ src/app/(site)/me/page.tsx | 110 ++++---- src/app/client/client-view.tsx | 15 +- update-Nitrov3.sh | 486 +++++++++++++++++++++++++++++---- 4 files changed, 593 insertions(+), 114 deletions(-) create mode 100644 scripts/sync-nitro-urls.cjs diff --git a/scripts/sync-nitro-urls.cjs b/scripts/sync-nitro-urls.cjs new file mode 100644 index 00000000..9e3e3e3f --- /dev/null +++ b/scripts/sync-nitro-urls.cjs @@ -0,0 +1,96 @@ +// Syncs the Nitro/Octane runtime config URLs from environment variables into the +// renderer-config.json / renderer-config.jsonc / ui-config.json files. +// +// Uses jsonc-parser so JSONC (with // and /* */ comments) is handled safely — +// unlike a naive JSON.parse/json.load this never corrupts URLs that contain +// "https://". After this runs the files are written back as strict JSON (which +// is still valid JSONC), so downstream validation passes. +// +// Env: +// NITRO_SRC_DIR e.g. /var/www/Octane/public/configuration +// NITRO_DIST_CONFIG_DIR e.g. /var/www/Octane/dist/configuration +// NITRO_GAMEDATA_CONF_DIR e.g. /var/www/Gamedata/config +// NITRO_IMAGE_LIBRARY_URL, NITRO_HOF_FURNITURE_URL, NITRO_API_URL, +// NITRO_SOCKET_URL, NITRO_GAMEDATA_URL, NITRO_ASSET_URL, +// NITRO_FURNI_ASSET_ICON_URL +const fs = require("node:fs"); +const path = require("node:path"); +const { parse, modify } = require("jsonc-parser"); + +const FILES = ["renderer-config.json", "renderer-config.jsonc", "ui-config.json"]; + +function env(name) { + return process.env[name] && process.env[name].length ? process.env[name] : null; +} + +function applyOverrides(data) { + const image = env("NITRO_IMAGE_LIBRARY_URL"); + const hof = env("NITRO_HOF_FURNITURE_URL"); + const api = env("NITRO_API_URL"); + const socket = env("NITRO_SOCKET_URL"); + const gamedata = env("NITRO_GAMEDATA_URL"); + const asset = env("NITRO_ASSET_URL"); + const icon = env("NITRO_FURNI_ASSET_ICON_URL"); + + const set = (key, value) => { + if (value && !(key in data)) data[key] = value; + if (value) data[key] = value; + }; + + set("image.library.url", image); + set("hof.furni.url", hof); + set("api.url", api); + set("socket.url", socket); + set("gamedata.url", gamedata); + set("asset.url", asset); + set("furni.asset.icon.url", icon); + + if (gamedata) { + data["radio.url"] = `${gamedata}/config/radio-stations.jsonc?t=%timestamp%`; + data["soundboard.url"] = `${gamedata}/config/soundboard-sounds.jsonc?t=%timestamp%`; + } + if ("show.google.ads" in data) data["show.google.ads"] = false; + return data; +} + +const dirs = [ + env("NITRO_SRC_DIR"), + env("NITRO_DIST_CONFIG_DIR"), + env("NITRO_GAMEDATA_CONF_DIR"), +].filter(Boolean); + +let changed = 0; +for (const dir of dirs) { + if (!fs.existsSync(dir)) continue; + for (const file of FILES) { + const full = path.join(dir, file); + if (!fs.existsSync(full)) continue; + let content; + try { + content = fs.readFileSync(full, "utf-8"); + } catch { + continue; + } + let data; + try { + data = parse(content, [], { allowTrailingComma: true, allowEmptyContent: true }); + } catch (e) { + console.error(`[sync-nitro-urls] parse error in ${full}: ${e}`); + continue; + } + if (!data || typeof data !== "object") continue; + const before = JSON.stringify(data); + const updated = applyOverrides(data); + if (JSON.stringify(updated) === before) continue; + try { + fs.writeFileSync(full, `${JSON.stringify(updated, null, 4)}\n`); + changed++; + console.log(` [OK] Synced URLs: ${file} (${dir})`); + } catch (e) { + console.error(`[sync-nitro-urls] write error ${full}: ${e}`); + } + } +} + +console.log(` [OK] URL sync complete (${changed} file(s) updated)`); +process.exit(0); diff --git a/src/app/(site)/me/page.tsx b/src/app/(site)/me/page.tsx index a65eecb5..660eb7c9 100644 --- a/src/app/(site)/me/page.tsx +++ b/src/app/(site)/me/page.tsx @@ -1,4 +1,4 @@ -import { and, asc, count, desc, eq, gt, inArray, or } from "drizzle-orm"; +import { and, asc, count, desc, eq, gt, or } from "drizzle-orm"; import Image from "next/image"; import Link from "next/link"; import { redirect } from "next/navigation"; @@ -17,7 +17,6 @@ import { UserReferrals, UsersBadges, UsersSettings, - WebsiteLoginLogs, } from "@/lib/db"; import { avatarImageUrl } from "@/lib/format"; import { resolveHotelName } from "@/lib/hotel-name"; @@ -58,10 +57,7 @@ const ERROR_MESSAGES: Record = { }; function getErrorMessage(error: string): string { - if (error === "not_enough") return ERROR_MESSAGES.not_enough; - if (error === "no_referrals") return ERROR_MESSAGES.no_referrals; - if (error === "bad_config") return ERROR_MESSAGES.bad_config; - return ERROR_MESSAGES.error; + return ERROR_MESSAGES[error] ?? ERROR_MESSAGES.error; } export default async function MePage({ @@ -90,10 +86,11 @@ export default async function MePage({ alertRaw, recentRooms, badges, - lastWebLoginRows, userSettingsRows, friendCountRows, unreadCountRows, + referralsRows, + friends, ] = await Promise.all([ db .select({ @@ -139,13 +136,6 @@ export default async function MePage({ .where(and(eq(UsersBadges.userId, userId), gt(UsersBadges.slotId, 0))) .orderBy(asc(UsersBadges.slotId)) .catch(() => []), - db - .select({ createdAt: WebsiteLoginLogs.createdAt }) - .from(WebsiteLoginLogs) - .where(eq(WebsiteLoginLogs.userId, userId)) - .orderBy(desc(WebsiteLoginLogs.id)) - .limit(1) - .catch(() => []), db .select({ achievementScore: UsersSettings.achievementScore, @@ -170,18 +160,51 @@ export default async function MePage({ .from(MessengerOffline) .where(eq(MessengerOffline.userId, userId)) .catch(() => [{ value: 0 }]), + db + .select({ referralsTotal: UserReferrals.referralsTotal }) + .from(UserReferrals) + .where(eq(UserReferrals.userId, userId)) + .orderBy(desc(UserReferrals.id)) + .limit(1) + .catch(() => []), + db + .select({ + id: User.id, + username: User.username, + look: User.look, + motto: User.motto, + online: User.online, + }) + .from(MessengerFriendships) + .innerJoin( + User, + or( + and( + eq(MessengerFriendships.userOneId, userId), + eq(User.id, MessengerFriendships.userTwoId), + ), + and( + eq(MessengerFriendships.userTwoId, userId), + eq(User.id, MessengerFriendships.userOneId), + ), + ), + ) + .where( + or( + eq(MessengerFriendships.userOneId, userId), + eq(MessengerFriendships.userTwoId, userId), + ), + ) + .catch(() => []), ]); const user = userRows[0] ?? null; - const lastWebLogin = lastWebLoginRows[0] ?? null; + if (!user) throw new Error("user-not-found"); + const userSettings = userSettingsRows[0] ?? null; const friendCount = Number(friendCountRows[0]?.value ?? 0); const unreadCount = Number(unreadCountRows[0]?.value ?? 0); - void lastWebLogin; - - if (!user) throw new Error("user-not-found"); - const needed = Number.parseInt(neededRaw ?? "5", 10) || 5; const rewardAmount = Number.parseInt(rewardAmountRaw ?? "30", 10) || 0; const rewardCurrency = ( @@ -192,53 +215,12 @@ export default async function MePage({ .trim() .toLowerCase(); - const [referrals] = await db - .select({ referralsTotal: UserReferrals.referralsTotal }) - .from(UserReferrals) - .where(eq(UserReferrals.userId, userId)) - .orderBy(desc(UserReferrals.id)) - .limit(1) - .catch(() => []); - const referralTotal = referrals ? Number(referrals.referralsTotal) : 0; + const referralTotal = referralsRows[0] + ? Number(referralsRows[0].referralsTotal) + : 0; const canClaim = referralTotal >= needed; const remaining = Math.max(0, needed - referralTotal); - const friendships = await db - .select({ - userOneId: MessengerFriendships.userOneId, - userTwoId: MessengerFriendships.userTwoId, - }) - .from(MessengerFriendships) - .where( - or( - eq(MessengerFriendships.userOneId, userId), - eq(MessengerFriendships.userTwoId, userId), - ), - ) - .catch(() => []); - - const friendIds = Array.from( - new Set( - friendships - .map((f) => (f.userOneId === userId ? f.userTwoId : f.userOneId)) - .filter((id) => id && id !== userId), - ), - ); - - const friends = friendIds.length - ? await db - .select({ - id: User.id, - username: User.username, - look: User.look, - motto: User.motto, - online: User.online, - }) - .from(User) - .where(inArray(User.id, friendIds)) - .catch(() => []) - : []; - const onlineFriends = friends.filter((f) => f.online === "1"); const registered = new Date(user.accountCreated * 1000) @@ -255,6 +237,7 @@ export default async function MePage({ const avatarFull = avatarImageUrl(user.look, { direction: 2, }); + content = (
{claimed ? ( @@ -298,6 +281,7 @@ export default async function MePage({ /> + {children} ); @@ -377,9 +383,12 @@ export function ClientView({