From 740e3c8932800fa1687633f3c6ee6428ee4d63da Mon Sep 17 00:00:00 2001 From: ic3w0lf Date: Tue, 3 Jun 2025 15:58:33 -0600 Subject: [PATCH 1/3] API_HOST changes, thumbnail fix & cache, "list is empty" fix API_HOST was replaced in order for thumbnail/any redirects to work properly, this also assumes the API will be at `{BASE_URL}/api`, assuming the reverse proxy causes issues with the way redirects were initially setup to work. Also no more "Submissions list is empty." while it's loading. --- README.md | 4 +- web/src/app/mapfixes/page.tsx | 6 +-- web/src/app/submissions/page.tsx | 6 +-- .../app/thumbnails/asset/[assetId]/route.tsx | 19 ++++++++++ web/src/app/thumbnails/maps/[mapId]/route.tsx | 9 +++-- web/src/middleware.ts | 37 ++++++++++--------- 6 files changed, 53 insertions(+), 28 deletions(-) diff --git a/README.md b/README.md index 75dc884..6c82469 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ Prerequisite: golang installed Prerequisite: bun installed -The environment variables `API_HOST` and `AUTH_HOST` will need to be set for the middleware. +The environment variables `BASE_URL` and `AUTH_HOST` will need to be set for the middleware. Example `.env` in web's root: ``` -API_HOST="http://localhost:8082/v1/" +BASE_URL="http://localhost:8082/" AUTH_HOST="http://localhost:8083/" ``` diff --git a/web/src/app/mapfixes/page.tsx b/web/src/app/mapfixes/page.tsx index 98b31e2..4d86e66 100644 --- a/web/src/app/mapfixes/page.tsx +++ b/web/src/app/mapfixes/page.tsx @@ -11,7 +11,7 @@ import "./(styles)/page.scss"; import { ListSortConstants } from "../ts/Sort"; export default function MapfixInfoPage() { - const [mapfixes, setMapfixes] = useState({Total:0,Mapfixes:[]}) + const [mapfixes, setMapfixes] = useState({Total:-1,Mapfixes:[]}) const [currentPage, setCurrentPage] = useState(1); const cardsPerPage = 24; // built to fit on a 1920x1080 monitor @@ -47,7 +47,7 @@ export default function MapfixInfoPage() { }, 50); }, [currentPage]) - if (!mapfixes) { + if (mapfixes.Total < 0) { return
Loading... @@ -55,7 +55,7 @@ export default function MapfixInfoPage() { } - if (mapfixes && mapfixes.Total == 0) { + if (mapfixes.Total == 0) { return
Mapfixes list is empty. diff --git a/web/src/app/submissions/page.tsx b/web/src/app/submissions/page.tsx index 8a7c017..1edfb9f 100644 --- a/web/src/app/submissions/page.tsx +++ b/web/src/app/submissions/page.tsx @@ -9,7 +9,7 @@ import "./(styles)/page.scss"; import { ListSortConstants } from "../ts/Sort"; export default function SubmissionInfoPage() { - const [submissions, setSubmissions] = useState({Total:0,Submissions:[]}) + const [submissions, setSubmissions] = useState({Total:-1,Submissions:[]}) const [currentPage, setCurrentPage] = useState(1); const cardsPerPage = 24; // built to fit on a 1920x1080 monitor @@ -45,7 +45,7 @@ export default function SubmissionInfoPage() { }, 50); }, [currentPage]) - if (!submissions) { + if (submissions.Total < 0) { return
Loading... @@ -53,7 +53,7 @@ export default function SubmissionInfoPage() { } - if (submissions && submissions.Total == 0) { + if (submissions.Total == 0) { return
Submissions list is empty. diff --git a/web/src/app/thumbnails/asset/[assetId]/route.tsx b/web/src/app/thumbnails/asset/[assetId]/route.tsx index b7992f4..e7232d8 100644 --- a/web/src/app/thumbnails/asset/[assetId]/route.tsx +++ b/web/src/app/thumbnails/asset/[assetId]/route.tsx @@ -1,5 +1,8 @@ import { NextRequest, NextResponse } from 'next/server'; +const cache = new Map(); +const CACHE_TTL = 15 * 60 * 1000; + export async function GET( request: NextRequest, context: { params: Promise<{ assetId: number }> } @@ -27,6 +30,19 @@ export async function GET( } } catch { } + const now = Date.now(); + const cached = cache.get(finalAssetId); + + if (cached && cached.expires > now) { + return new NextResponse(cached.buffer, { + headers: { + 'Content-Type': 'image/png', + 'Content-Length': cached.buffer.length.toString(), + 'Cache-Control': `public, max-age=${CACHE_TTL / 1000}`, + }, + }); + } + try { const response = await fetch( `https://thumbnails.roblox.com/v1/assets?format=png&size=512x512&assetIds=${finalAssetId}` @@ -54,10 +70,13 @@ export async function GET( const arrayBuffer = await imageResponse.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); + cache.set(finalAssetId, { buffer, expires: now + CACHE_TTL }); + return new NextResponse(buffer, { headers: { 'Content-Type': 'image/png', 'Content-Length': buffer.length.toString(), + 'Cache-Control': `public, max-age=${CACHE_TTL / 1000}`, }, }); } catch { diff --git a/web/src/app/thumbnails/maps/[mapId]/route.tsx b/web/src/app/thumbnails/maps/[mapId]/route.tsx index 174feee..a923d21 100644 --- a/web/src/app/thumbnails/maps/[mapId]/route.tsx +++ b/web/src/app/thumbnails/maps/[mapId]/route.tsx @@ -5,9 +5,12 @@ export async function GET( context: { params: Promise<{ mapId: string }> } ): Promise { // TODO: implement this, we need a cdn for in-game map thumbnails... - + const { mapId } = await context.params; - const baseUrl = request.nextUrl.origin; // Gets the current base URL - return NextResponse.redirect(`${baseUrl}/thumbnails/asset/${mapId}`); + const protocol = request.headers.get("x-forwarded-proto") || "https"; + const host = request.headers.get("host"); + const origin = `${protocol}://${host}`; + + return NextResponse.redirect(`${origin}/thumbnails/asset/${mapId}`); } \ No newline at end of file diff --git a/web/src/middleware.ts b/web/src/middleware.ts index 0a25a76..4bdf6cc 100644 --- a/web/src/middleware.ts +++ b/web/src/middleware.ts @@ -1,29 +1,32 @@ import { NextRequest, NextResponse } from "next/server" export const config = { - matcher: ["/api/:path*", "/auth/:path*"], + matcher: ["/api/:path*", "/auth/:path*"], } export function middleware(request: NextRequest) { - const { pathname, search } = request.nextUrl + const { pathname, search } = request.nextUrl - if (pathname.startsWith("/api")) { - if (!process.env.API_HOST) { - throw new Error('env variable "API_HOST" is not set') - } - const apiUrl = new URL(process.env.API_HOST + pathname.replace(/^\/api/, '') + search) - return NextResponse.rewrite(apiUrl, { request }) - } else if (pathname.startsWith("/auth")) { + if (pathname.startsWith("/api")) { + if (!process.env.BASE_URL) { + throw new Error('env variable "BASE_URL" is not set') + } + + const baseUrl = process.env.BASE_URL.replace(/\/$/, ""); + const apiUrl = new URL(baseUrl + pathname + search); + + return NextResponse.rewrite(apiUrl, { request }); + } else if (pathname.startsWith("/auth")) { if (!process.env.AUTH_HOST) { throw new Error('env variable "AUTH_HOST" is not set') } - - const authHost = process.env.AUTH_HOST.replace(/\/$/, "") - const path = pathname.replace(/^\/auth/, "") - const redirectUrl = new URL(authHost + path + search) - - return NextResponse.redirect(redirectUrl, 302) - } + + const authHost = process.env.AUTH_HOST.replace(/\/$/, ""); + const path = pathname.replace(/^\/auth/, ""); + const redirectUrl = new URL(authHost + path + search); - return NextResponse.next() + return NextResponse.redirect(redirectUrl, 302); + } + + return NextResponse.next() } \ No newline at end of file -- 2.49.1 From 954dbaeac65b782f6d5b8eee68412e7761324a69 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 3 Jun 2025 16:27:42 -0700 Subject: [PATCH 2/3] env var name change requires deployment configuration change --- README.md | 4 ++-- web/src/middleware.ts | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6c82469..7930a81 100644 --- a/README.md +++ b/README.md @@ -26,10 +26,10 @@ Prerequisite: golang installed Prerequisite: bun installed -The environment variables `BASE_URL` and `AUTH_HOST` will need to be set for the middleware. +The environment variables `API_HOST` and `AUTH_HOST` will need to be set for the middleware. Example `.env` in web's root: ``` -BASE_URL="http://localhost:8082/" +API_HOST="http://localhost:8082/" AUTH_HOST="http://localhost:8083/" ``` diff --git a/web/src/middleware.ts b/web/src/middleware.ts index 4bdf6cc..61da104 100644 --- a/web/src/middleware.ts +++ b/web/src/middleware.ts @@ -8,11 +8,11 @@ export function middleware(request: NextRequest) { const { pathname, search } = request.nextUrl if (pathname.startsWith("/api")) { - if (!process.env.BASE_URL) { - throw new Error('env variable "BASE_URL" is not set') + if (!process.env.API_HOST) { + throw new Error('env variable "API_HOST" is not set') } - - const baseUrl = process.env.BASE_URL.replace(/\/$/, ""); + + const baseUrl = process.env.API_HOST.replace(/\/$/, ""); const apiUrl = new URL(baseUrl + pathname + search); return NextResponse.rewrite(apiUrl, { request }); @@ -20,13 +20,13 @@ export function middleware(request: NextRequest) { if (!process.env.AUTH_HOST) { throw new Error('env variable "AUTH_HOST" is not set') } - + const authHost = process.env.AUTH_HOST.replace(/\/$/, ""); const path = pathname.replace(/^\/auth/, ""); const redirectUrl = new URL(authHost + path + search); return NextResponse.redirect(redirectUrl, 302); - } + } return NextResponse.next() -} \ No newline at end of file +} -- 2.49.1 From 8ea5ee2d410ecbddaee3b528faffb246fdcfde48 Mon Sep 17 00:00:00 2001 From: Quaternions Date: Tue, 3 Jun 2025 16:29:29 -0700 Subject: [PATCH 3/3] use null instead of sentinel value --- web/src/app/mapfixes/page.tsx | 44 ++++++++++++++++---------------- web/src/app/submissions/page.tsx | 44 ++++++++++++++++---------------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/web/src/app/mapfixes/page.tsx b/web/src/app/mapfixes/page.tsx index 4d86e66..e4674b6 100644 --- a/web/src/app/mapfixes/page.tsx +++ b/web/src/app/mapfixes/page.tsx @@ -11,10 +11,31 @@ import "./(styles)/page.scss"; import { ListSortConstants } from "../ts/Sort"; export default function MapfixInfoPage() { - const [mapfixes, setMapfixes] = useState({Total:-1,Mapfixes:[]}) + const [mapfixes, setMapfixes] = useState(null) const [currentPage, setCurrentPage] = useState(1); const cardsPerPage = 24; // built to fit on a 1920x1080 monitor + useEffect(() => { + async function fetchMapfixes() { + const res = await fetch(`/api/mapfixes?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`) + if (res.ok) { + setMapfixes(await res.json()) + } + } + + setTimeout(() => { + fetchMapfixes() + }, 50); + }, [currentPage]) + + if (!mapfixes) { + return +
+ Loading... +
+
+ } + const totalPages = Math.ceil(mapfixes.Total / cardsPerPage); const currentCards = mapfixes.Mapfixes.slice( @@ -34,27 +55,6 @@ export default function MapfixInfoPage() { } }; - useEffect(() => { - async function fetchMapfixes() { - const res = await fetch(`/api/mapfixes?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`) - if (res.ok) { - setMapfixes(await res.json()) - } - } - - setTimeout(() => { - fetchMapfixes() - }, 50); - }, [currentPage]) - - if (mapfixes.Total < 0) { - return -
- Loading... -
-
- } - if (mapfixes.Total == 0) { return
diff --git a/web/src/app/submissions/page.tsx b/web/src/app/submissions/page.tsx index 1edfb9f..bd67b33 100644 --- a/web/src/app/submissions/page.tsx +++ b/web/src/app/submissions/page.tsx @@ -9,10 +9,31 @@ import "./(styles)/page.scss"; import { ListSortConstants } from "../ts/Sort"; export default function SubmissionInfoPage() { - const [submissions, setSubmissions] = useState({Total:-1,Submissions:[]}) + const [submissions, setSubmissions] = useState(null) const [currentPage, setCurrentPage] = useState(1); const cardsPerPage = 24; // built to fit on a 1920x1080 monitor + useEffect(() => { + async function fetchSubmissions() { + const res = await fetch(`/api/submissions?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`) + if (res.ok) { + setSubmissions(await res.json()) + } + } + + setTimeout(() => { + fetchSubmissions() + }, 50); + }, [currentPage]) + + if (!submissions) { + return +
+ Loading... +
+
+ } + const totalPages = Math.ceil(submissions.Total / cardsPerPage); const currentCards = submissions.Submissions.slice( @@ -32,27 +53,6 @@ export default function SubmissionInfoPage() { } }; - useEffect(() => { - async function fetchSubmissions() { - const res = await fetch(`/api/submissions?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`) - if (res.ok) { - setSubmissions(await res.json()) - } - } - - setTimeout(() => { - fetchSubmissions() - }, 50); - }, [currentPage]) - - if (submissions.Total < 0) { - return -
- Loading... -
-
- } - if (submissions.Total == 0) { return
-- 2.49.1