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) =>
- `
`,
- );
-
+ 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 `
-
-
-
-
-
${comment}
+
+
+
+
-
- `;
+
${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;
}
}