diff --git a/.gitignore b/.gitignore index 5030290..3f0b6ea 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,3 @@ bun.lock .env logs/ .vscode/ -robots.txt diff --git a/config/environment.ts b/config/environment.ts index a4da0eb..dec203b 100644 --- a/config/environment.ts +++ b/config/environment.ts @@ -1,69 +1,27 @@ -import { resolve } from "node:path"; -import { logger } from "@creations.works/logger"; - -const environment: Environment = { +export const environment: Environment = { port: Number.parseInt(process.env.PORT || "8080", 10), host: process.env.HOST || "0.0.0.0", development: process.env.NODE_ENV === "development" || process.argv.includes("--dev"), }; -const redisTtl: number = process.env.REDIS_TTL +export const redisTtl: number = process.env.REDIS_TTL ? Number.parseInt(process.env.REDIS_TTL, 10) : 60 * 60 * 1; // 1 hour -const lanyardConfig: LanyardConfig = { +export const lanyardConfig: LanyardConfig = { userId: process.env.LANYARD_USER_ID || "", - instance: process.env.LANYARD_INSTANCE || "", + instance: process.env.LANYARD_INSTANCE || "https://api.lanyard.rest", }; -const reviewDb = { +export const reviewDb = { enabled: process.env.REVIEW_DB === "true" || process.env.REVIEW_DB === "1", url: "https://manti.vendicated.dev/api/reviewdb", }; -const badgeApi: string | null = process.env.BADGE_API_URL || null; -const steamGridDbKey: string | undefined = process.env.STEAMGRIDDB_API_KEY; +export const badgeApi: string | null = process.env.BADGE_API_URL || null; +export const steamGridDbKey: string | undefined = + process.env.STEAMGRIDDB_API_KEY; -const plausibleScript: string | null = +export const plausibleScript: string | null = process.env.PLAUSIBLE_SCRIPT_HTML?.trim() || null; - -const robotstxtPath: string | null = process.env.ROBOTS_FILE - ? resolve(process.env.ROBOTS_FILE) - : null; - -function verifyRequiredVariables(): void { - const requiredVariables = [ - "HOST", - "PORT", - - "LANYARD_USER_ID", - "LANYARD_INSTANCE", - ]; - - let hasError = false; - - for (const key of requiredVariables) { - const value = process.env[key]; - if (value === undefined || value.trim() === "") { - logger.error(`Missing or empty environment variable: ${key}`); - hasError = true; - } - } - - if (hasError) { - process.exit(1); - } -} - -export { - environment, - lanyardConfig, - redisTtl, - reviewDb, - badgeApi, - steamGridDbKey, - plausibleScript, - robotstxtPath, - verifyRequiredVariables, -}; diff --git a/public/css/index.css b/public/css/index.css index b8df88e..1593bf9 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -884,13 +884,6 @@ ul { flex-wrap: wrap; } -.review-header-inner { - display: flex; - flex-direction: row; - align-items: center; - gap: 0.5rem; -} - .review-username { font-weight: 600; color: var(--text-color); @@ -914,23 +907,6 @@ ul { flex-wrap: wrap; } -.emoji { - width: 20px; - height: 20px; - vertical-align: middle; - margin: 0 2px; - display: inline-block; - transition: transform 0.3s ease; -} - -.emoji:hover { - transform: scale(1.2); -} - -.review-content img.emoji { - vertical-align: middle; -} - @media (max-width: 600px) { .reviews { max-width: 100%; @@ -989,9 +965,4 @@ ul { .review-badges { justify-content: center; } - - .emoji { - width: 16px; - height: 16px; - } } diff --git a/public/js/index.js b/public/js/index.js index 4c0bfc4..1193627 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -142,7 +142,7 @@ async function populateReviews(userId) { const data = await res.json(); if (!data.success || !Array.isArray(data.reviews)) { - if (currentReviewOffset === 0) reviewSection.classList.add("hidden"); + if (page === 1) reviewSection.classList.add("hidden"); isLoadingReviews = false; return; } @@ -152,14 +152,7 @@ async function populateReviews(userId) { const sender = review.sender; const username = sender.username; const avatar = sender.profilePhoto; - let comment = review.comment; - - comment = comment.replace( - /<(a?):\w+:(\d+)>/g, - (_, animated, id) => - `emoji`, - ); - + const comment = review.comment; const timestamp = review.timestamp ? new Date(review.timestamp * 1000).toLocaleString(undefined, { hour12: false, @@ -179,20 +172,18 @@ async function populateReviews(userId) { .join(""); return ` -
  • - ${username}'s avatar -
    -
    -
    - ${username} - ${badges} -
    - ${timestamp} -
    -
    ${comment}
    +
  • + ${username}'s avatar +
    +
    + ${username} + ${timestamp}
    -
  • - `; +
    ${badges}
    +
    ${comment}
    + + + `; }) .join(""); @@ -202,6 +193,7 @@ async function populateReviews(userId) { reviewSection.classList.remove("hidden"); hasMoreReviews = data.hasNextPage; + currentReviewPage = page; isLoadingReviews = false; } catch (err) { console.error("Failed to fetch reviews", err); diff --git a/src/index.ts b/src/index.ts index 692b84e..2836a3f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,7 @@ import { serverHandler } from "@/server"; -import { verifyRequiredVariables } from "@config/environment"; import { logger } from "@creations.works/logger"; async function main(): Promise { - verifyRequiredVariables(); serverHandler.initialize(); } diff --git a/src/server.ts b/src/server.ts index c936afa..2bc28ae 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,5 +1,5 @@ import { resolve } from "node:path"; -import { environment, robotstxtPath } from "@config/environment"; +import { environment } from "@config/environment"; import { logger } from "@creations.works/logger"; import { type BunFile, @@ -65,15 +65,10 @@ class ServerHandler { } } - private async serveStaticFile( - request: ExtendedRequest, - pathname: string, - ip: string, - ): Promise { - let filePath: string; - let response: Response; - + private async serveStaticFile(pathname: string): Promise { try { + let filePath: string; + if (pathname === "/favicon.ico") { filePath = resolve("public", "assets", "favicon.ico"); } else { @@ -86,37 +81,16 @@ class ServerHandler { const fileContent: ArrayBuffer = await file.arrayBuffer(); const contentType: string = file.type || "application/octet-stream"; - response = new Response(fileContent, { + return new Response(fileContent, { headers: { "Content-Type": contentType }, }); - } else { - logger.warn(`File not found: ${filePath}`); - response = new Response("Not Found", { status: 404 }); } + logger.warn(`File not found: ${filePath}`); + return new Response("Not Found", { status: 404 }); } catch (error) { logger.error([`Error serving static file: ${pathname}`, error as Error]); - response = new Response("Internal Server Error", { status: 500 }); + return new Response("Internal Server Error", { status: 500 }); } - - this.logRequest(request, response, ip); - return response; - } - - private logRequest( - request: ExtendedRequest, - response: Response, - ip: string | undefined, - ): void { - logger.custom( - `[${request.method}]`, - `(${response.status})`, - [ - request.url, - `${(performance.now() - request.startPerf).toFixed(2)}ms`, - ip || "unknown", - ], - "90", - ); } private async handleRequest( @@ -126,52 +100,16 @@ class ServerHandler { const extendedRequest: ExtendedRequest = request as ExtendedRequest; extendedRequest.startPerf = performance.now(); - const headers = request.headers; - let ip = server.requestIP(request)?.address; - let response: Response; - - if (!ip || ip.startsWith("172.") || ip === "127.0.0.1") { - ip = - headers.get("CF-Connecting-IP")?.trim() || - headers.get("X-Real-IP")?.trim() || - headers.get("X-Forwarded-For")?.split(",")[0].trim() || - "unknown"; - } - const pathname: string = new URL(request.url).pathname; - if (pathname === "/robots.txt" && robotstxtPath) { - try { - const file: BunFile = Bun.file(robotstxtPath); - - if (await file.exists()) { - const fileContent: ArrayBuffer = await file.arrayBuffer(); - const contentType: string = file.type || "text/plain"; - - response = new Response(fileContent, { - headers: { "Content-Type": contentType }, - }); - } else { - logger.warn(`File not found: ${robotstxtPath}`); - response = new Response("Not Found", { status: 404 }); - } - } catch (error) { - logger.error([ - `Error serving robots.txt: ${robotstxtPath}`, - error as Error, - ]); - response = new Response("Internal Server Error", { status: 500 }); - } - - this.logRequest(extendedRequest, response, ip); - return response; - } - if (pathname.startsWith("/public") || pathname === "/favicon.ico") { - return await this.serveStaticFile(extendedRequest, pathname, ip); + return await this.serveStaticFile(pathname); } const match: MatchedRoute | null = this.router.match(request); let requestBody: unknown = {}; + let response: Response; + + let logRequest = true; if (match) { const { filePath, params, query } = match; @@ -183,6 +121,8 @@ class ServerHandler { ? contentType.split(";")[0].trim() : null; + logRequest = routeModule.routeDef.log !== false; + if ( routeModule.routeDef.needsBody === "json" && actualContentType === "application/json" @@ -291,6 +231,30 @@ class ServerHandler { ); } + if (logRequest) { + const headers = request.headers; + let ip = server.requestIP(request)?.address; + + if (!ip || ip.startsWith("172.") || ip === "127.0.0.1") { + ip = + headers.get("CF-Connecting-IP")?.trim() || + headers.get("X-Real-IP")?.trim() || + headers.get("X-Forwarded-For")?.split(",")[0].trim() || + "unknown"; + } + + logger.custom( + `[${request.method}]`, + `(${response.status})`, + [ + request.url, + `${(performance.now() - extendedRequest.startPerf).toFixed(2)}ms`, + ip || "unknown", + ], + "90", + ); + } + return response; } }