API_HOST changes, thumbnail fix & cache, "list is empty" fix #150

Merged
ic3w0lf22 merged 4 commits from thumbnail-fix into staging 2025-06-03 23:54:13 +00:00
6 changed files with 89 additions and 64 deletions

View File

@@ -29,7 +29,7 @@ Prerequisite: bun installed
The environment variables `API_HOST` and `AUTH_HOST` will need to be set for the middleware.
Example `.env` in web's root:
```
API_HOST="http://localhost:8082/v1/"
API_HOST="http://localhost:8082/"
AUTH_HOST="http://localhost:8083/"
```

View File

@@ -11,29 +11,10 @@ import "./(styles)/page.scss";
import { ListSortConstants } from "../ts/Sort";
export default function MapfixInfoPage() {
const [mapfixes, setMapfixes] = useState<MapfixList>({Total:0,Mapfixes:[]})
const [mapfixes, setMapfixes] = useState<MapfixList|null>(null)
const [currentPage, setCurrentPage] = useState(1);
const cardsPerPage = 24; // built to fit on a 1920x1080 monitor
const totalPages = Math.ceil(mapfixes.Total / cardsPerPage);
const currentCards = mapfixes.Mapfixes.slice(
(currentPage - 1) * cardsPerPage,
currentPage * cardsPerPage
);
const nextPage = () => {
if (currentPage < totalPages) {
setCurrentPage(currentPage + 1);
}
};
const prevPage = () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
};
useEffect(() => {
async function fetchMapfixes() {
const res = await fetch(`/api/mapfixes?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`)
@@ -55,7 +36,26 @@ export default function MapfixInfoPage() {
</Webpage>
}
if (mapfixes && mapfixes.Total == 0) {
const totalPages = Math.ceil(mapfixes.Total / cardsPerPage);
const currentCards = mapfixes.Mapfixes.slice(
(currentPage - 1) * cardsPerPage,
currentPage * cardsPerPage
);
const nextPage = () => {
if (currentPage < totalPages) {
setCurrentPage(currentPage + 1);
}
};
const prevPage = () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
};
if (mapfixes.Total == 0) {
return <Webpage>
<main>
Mapfixes list is empty.

View File

@@ -9,29 +9,10 @@ import "./(styles)/page.scss";
import { ListSortConstants } from "../ts/Sort";
export default function SubmissionInfoPage() {
const [submissions, setSubmissions] = useState<SubmissionList>({Total:0,Submissions:[]})
const [submissions, setSubmissions] = useState<SubmissionList|null>(null)
const [currentPage, setCurrentPage] = useState(1);
const cardsPerPage = 24; // built to fit on a 1920x1080 monitor
const totalPages = Math.ceil(submissions.Total / cardsPerPage);
const currentCards = submissions.Submissions.slice(
(currentPage - 1) * cardsPerPage,
currentPage * cardsPerPage
);
const nextPage = () => {
if (currentPage < totalPages) {
setCurrentPage(currentPage + 1);
}
};
const prevPage = () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
};
useEffect(() => {
async function fetchSubmissions() {
const res = await fetch(`/api/submissions?Page=${currentPage}&Limit=${cardsPerPage}&Sort=${ListSortConstants.ListSortDateDescending}`)
@@ -53,7 +34,26 @@ export default function SubmissionInfoPage() {
</Webpage>
}
if (submissions && submissions.Total == 0) {
const totalPages = Math.ceil(submissions.Total / cardsPerPage);
const currentCards = submissions.Submissions.slice(
(currentPage - 1) * cardsPerPage,
currentPage * cardsPerPage
);
const nextPage = () => {
if (currentPage < totalPages) {
setCurrentPage(currentPage + 1);
}
};
const prevPage = () => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
};
if (submissions.Total == 0) {
return <Webpage>
<main>
Submissions list is empty.

View File

@@ -1,5 +1,8 @@
import { NextRequest, NextResponse } from 'next/server';
const cache = new Map<number, { buffer: Buffer; expires: number }>();
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 {

View File

@@ -5,9 +5,12 @@ export async function GET(
context: { params: Promise<{ mapId: string }> }
): Promise<NextResponse> {
// 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}`);
}

View File

@@ -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.API_HOST) {
throw new Error('env variable "API_HOST" is not set')
}
const baseUrl = process.env.API_HOST.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)
}
return NextResponse.next()
}
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()
}