diff --git a/.env.example b/.env.example index a10f7a4..626a366 100644 --- a/.env.example +++ b/.env.example @@ -15,8 +15,6 @@ BADGE_API_URL=http://localhost:8081 # Required if you want to enable reviews from reviewdb REVIEW_DB=true -#Timezone api url, aka: https://git.creations.works/creations/timezoneDB -TIMEZONE_API_URL= # https://www.steamgriddb.com/api/v2, if you want games to have images STEAMGRIDDB_API_KEY=steamgrid_api_key diff --git a/.forgejo/workflows/docker-release.yml b/.forgejo/workflows/docker-release.yml deleted file mode 100644 index 110bc88..0000000 --- a/.forgejo/workflows/docker-release.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Docker Image Release - -on: - push: - tags: - - "v*" - -jobs: - release: - runs-on: docker - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Set tag - run: echo "TAG=${GITHUB_REF##*/}" >> $GITHUB_ENV - - - name: Login to Docker Registry - run: echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login -u "${{ secrets.REGISTRY_USERNAME }}" --password-stdin - - - name: Build and Push Docker Image - run: | - IMAGE=creations/profile-page - docker build --target release -t $IMAGE:$TAG -t $IMAGE:latest . - docker push $IMAGE:$TAG - docker push $IMAGE:latest diff --git a/.gitignore b/.gitignore index 16498de..5030290 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,3 @@ bun.lock logs/ .vscode/ robots.txt -public/custom diff --git a/README.md b/README.md index b4a85fd..3b0cf84 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,8 @@ cp .env.example .env | `LANYARD_INSTANCE` | Endpoint of the Lanyard instance | | `BADGE_API_URL` | Badge API URL ([badgeAPI](https://git.creations.works/creations/badgeAPI)) | | `REVIEW_DB` | Enables showing reviews from reviewdb on user pages | -| `TIMEZONE_API_URL` | Enables showing times from [timezoneDB](https://git.creations.works/creations/timezoneDB) | | `STEAMGRIDDB_API_KEY` | SteamGridDB API key for fetching game icons | +| `ROBOTS_FILE` | If there it uses the file in /robots.txt route, requires a valid path | #### Optional Lanyard KV Variables (per-user customization) @@ -80,7 +80,6 @@ These can be defined in Lanyard's KV store to customize the page: | `css` | URL to a css to change styles on the page, no import or require allowed | | `optout` | Allows users to stop sharing there profile on the website (`true` / `false`) | | `reviews` | Enables reviews from reviewdb (`true` / `false`) | -| `timezone`| Enables the showing of the current time from the timezone db API (`true` / `false`) | --- diff --git a/config/environment.ts b/config/environment.ts index 2c89b09..a4da0eb 100644 --- a/config/environment.ts +++ b/config/environment.ts @@ -1,4 +1,5 @@ -import { echo } from "@atums/echo"; +import { resolve } from "node:path"; +import { logger } from "@creations.works/logger"; const environment: Environment = { port: Number.parseInt(process.env.PORT || "8080", 10), @@ -21,14 +22,16 @@ const reviewDb = { url: "https://manti.vendicated.dev/api/reviewdb", }; -const timezoneAPIUrl: string | null = process.env.TIMEZONE_API_URL || null; - const badgeApi: string | null = process.env.BADGE_API_URL || null; const steamGridDbKey: string | undefined = process.env.STEAMGRIDDB_API_KEY; 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", @@ -43,7 +46,7 @@ function verifyRequiredVariables(): void { for (const key of requiredVariables) { const value = process.env[key]; if (value === undefined || value.trim() === "") { - echo.error(`Missing or empty environment variable: ${key}`); + logger.error(`Missing or empty environment variable: ${key}`); hasError = true; } } @@ -58,9 +61,9 @@ export { lanyardConfig, redisTtl, reviewDb, - timezoneAPIUrl, badgeApi, steamGridDbKey, plausibleScript, + robotstxtPath, verifyRequiredVariables, }; diff --git a/logger.json b/logger.json deleted file mode 100644 index 521b3bc..0000000 --- a/logger.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "directory": "logs", - "level": "debug", - "disableFile": false, - - "rotate": true, - "maxFiles": 3, - - "console": true, - "consoleColor": true, - - "dateFormat": "yyyy-MM-dd HH:mm:ss.SSS", - "timezone": "local", - - "silent": false, - - "pattern": "{color:gray}{pretty-timestamp}{reset} {color:levelColor}[{level-name}]{reset} {color:gray}({reset}{file-name}:{color:blue}{line}{reset}:{color:blue}{column}{color:gray}){reset} {data}", - "levelColor": { - "debug": "blue", - "info": "green", - "warn": "yellow", - "error": "red", - "fatal": "red" - }, - - "customPattern": "{color:gray}{pretty-timestamp}{reset} {color:tagColor}[{tag}]{reset} {color:contextColor}({context}){reset} {data}", - "customColors": { - "GET": "green", - "POST": "blue", - "PUT": "yellow", - "DELETE": "red", - "PATCH": "cyan", - "HEAD": "magenta", - "OPTIONS": "white", - "TRACE": "gray" - }, - - "prettyPrint": true -} diff --git a/package.json b/package.json index 5654ef1..b42bfdb 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "typescript": "^5.8.3" }, "dependencies": { - "@atums/echo": "^1.0.3", + "@creations.works/logger": "^1.0.3", "marked": "^15.0.7" } } diff --git a/public/css/index.css b/public/css/index.css index 8960831..157f884 100644 --- a/public/css/index.css +++ b/public/css/index.css @@ -995,37 +995,3 @@ ul { height: 16px; } } - -/* timezone display */ - -.timezone-wrapper { - position: fixed; - top: 1rem; - right: 1rem; - background-color: var(--card-bg); - color: var(--text-color); - font-size: 0.9rem; - padding: 0.4rem 0.8rem; - border-radius: 6px; - border: 1px solid var(--border-color); - box-shadow: 0 0 8px rgba(0, 0, 0, 0.2); - z-index: 100; - user-select: none; - opacity: 0.85; - transition: opacity 0.2s ease; -} - -.timezone-wrapper:hover { - opacity: 1; -} - -.timezone-label { - color: var(--text-muted); - margin-right: 0.4rem; -} - -@media (max-width: 600px) { - .timezone-label { - display: none; - } -} diff --git a/public/js/index.js b/public/js/index.js index aaba9d6..bb2a3e1 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -3,7 +3,6 @@ const userId = head?.dataset.userId; const activityProgressMap = new Map(); const reviewURL = head?.dataset.reviewDb; -const timezoneApiUrl = head?.dataset.timezoneApi; let instanceUri = head?.dataset.instanceUri; let badgeURL = head?.dataset.badgeUrl; let socket; @@ -210,60 +209,6 @@ async function populateReviews(userId) { } } -function populateTimezone(userId) { - if (!userId || !timezoneApiUrl) return; - - let currentTimezone = null; - - async function fetchTimezone() { - try { - const res = await fetch( - `${timezoneApiUrl}/get?id=${encodeURIComponent(userId)}`, - ); - if (!res.ok) throw new Error("Failed to fetch timezone"); - - const json = await res.json(); - if (!json || typeof json.timezone !== "string") return; - - currentTimezone = json.timezone; - updateTime(); - } catch (err) { - console.error("Failed to populate timezone", err); - } - } - - function updateTime() { - if (!currentTimezone) return; - - const timezoneEl = document.querySelector(".timezone-value"); - if (!timezoneEl) return; - - const now = new Date(); - - const time24 = now.toLocaleTimeString("en-GB", { - timeZone: currentTimezone, - hour12: false, - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - }); - - const time12 = now.toLocaleTimeString("en-US", { - timeZone: currentTimezone, - hour12: true, - hour: "2-digit", - minute: "2-digit", - second: "2-digit", - }); - - timezoneEl.textContent = time24; - timezoneEl.title = `${time12} (${currentTimezone})`; - } - - fetchTimezone(); - setInterval(updateTime, 1000); -} - function setupReviewScrollObserver(userId) { const sentinel = document.createElement("div"); sentinel.className = "review-scroll-sentinel"; @@ -648,14 +593,6 @@ async function updatePresence(initialData) { setupReviewScrollObserver(userId); } - if (kv.timezone !== "false" && userId && timezoneApiUrl) { - populateTimezone(userId); - const timezoneEl = document.querySelector(".timezone-value"); - if (timezoneEl) { - timezoneEl.classList.remove("hidden"); - } - } - const platform = { mobile: data.active_on_discord_mobile, web: data.active_on_discord_web, @@ -818,7 +755,7 @@ function updateClanBadge(data) { const userInfoInner = document.querySelector(".user-info-inner"); if (!userInfoInner) return; - const clan = data?.discord_user?.primary_guild; + const clan = data?.discord_user?.clan; if (!clan || !clan.tag || !clan.identity_guild_id || !clan.badge) return; const existing = userInfoInner.querySelector(".clan-badge"); diff --git a/src/index.ts b/src/index.ts index 71c6418..692b84e 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import { serverHandler } from "@/server"; -import { echo } from "@atums/echo"; import { verifyRequiredVariables } from "@config/environment"; +import { logger } from "@creations.works/logger"; async function main(): Promise { verifyRequiredVariables(); @@ -8,7 +8,7 @@ async function main(): Promise { } main().catch((error: Error) => { - echo.error({ message: "Error initializing the server", error }); + logger.error(["Error initializing the server:", error]); process.exit(1); }); diff --git a/src/routes/[id].ts b/src/routes/[id].ts index 06c32e7..28921ec 100644 --- a/src/routes/[id].ts +++ b/src/routes/[id].ts @@ -4,7 +4,6 @@ import { lanyardConfig, plausibleScript, reviewDb, - timezoneAPIUrl, } from "@config/environment"; import { file } from "bun"; @@ -34,10 +33,6 @@ async function handler(request: ExtendedRequest): Promise { head.setAttribute("data-review-db", reviewDb.url); } - if (timezoneAPIUrl) { - head.setAttribute("data-timezone-api", timezoneAPIUrl); - } - if (plausibleScript) { head.append(plausibleScript, { html: true }); } diff --git a/src/server.ts b/src/server.ts index e851a58..c936afa 100644 --- a/src/server.ts +++ b/src/server.ts @@ -1,6 +1,6 @@ import { resolve } from "node:path"; -import { echo } from "@atums/echo"; -import { environment } from "@config/environment"; +import { environment, robotstxtPath } from "@config/environment"; +import { logger } from "@creations.works/logger"; import { type BunFile, FileSystemRouter, @@ -43,14 +43,16 @@ class ServerHandler { `http://127.0.0.1:${server.port}`, ]; - echo.info(`Server running at ${accessUrls[0]}`); - echo.info(`Access via: ${accessUrls[1]} or ${accessUrls[2]}`); + logger.info(`Server running at ${accessUrls[0]}`); + logger.info(`Access via: ${accessUrls[1]} or ${accessUrls[2]}`, { + breakLine: true, + }); this.logRoutes(); } private logRoutes(): void { - echo.info("Available routes:"); + logger.info("Available routes:"); const sortedRoutes: [string, string][] = Object.entries( this.router.routes, @@ -59,7 +61,7 @@ class ServerHandler { ); for (const [path, filePath] of sortedRoutes) { - echo.info(`Route: ${path}, File: ${filePath}`); + logger.info(`Route: ${path}, File: ${filePath}`); } } @@ -88,14 +90,11 @@ class ServerHandler { headers: { "Content-Type": contentType }, }); } else { - echo.warn(`File not found: ${filePath}`); + logger.warn(`File not found: ${filePath}`); response = new Response("Not Found", { status: 404 }); } } catch (error) { - echo.error({ - message: `Error serving static file: ${pathname}`, - error: error as Error, - }); + logger.error([`Error serving static file: ${pathname}`, error as Error]); response = new Response("Internal Server Error", { status: 500 }); } @@ -108,23 +107,16 @@ class ServerHandler { response: Response, ip: string | undefined, ): void { - const pathname = new URL(request.url).pathname; - - const ignoredStartsWith: string[] = ["/public"]; - const ignoredPaths: string[] = ["/favicon.ico"]; - - if ( - ignoredStartsWith.some((prefix) => pathname.startsWith(prefix)) || - ignoredPaths.includes(pathname) - ) { - return; - } - - echo.custom(`${request.method}`, `${response.status}`, [ - request.url, - `${(performance.now() - request.startPerf).toFixed(2)}ms`, - ip || "unknown", - ]); + logger.custom( + `[${request.method}]`, + `(${response.status})`, + [ + request.url, + `${(performance.now() - request.startPerf).toFixed(2)}ms`, + ip || "unknown", + ], + "90", + ); } private async handleRequest( @@ -147,21 +139,29 @@ class ServerHandler { } const pathname: string = new URL(request.url).pathname; + if (pathname === "/robots.txt" && robotstxtPath) { + try { + const file: BunFile = Bun.file(robotstxtPath); - const baseDir = resolve("public/custom"); - const customPath = resolve(baseDir, pathname.slice(1)); + if (await file.exists()) { + const fileContent: ArrayBuffer = await file.arrayBuffer(); + const contentType: string = file.type || "text/plain"; - if (!customPath.startsWith(baseDir)) { - return new Response("Forbidden", { status: 403 }); - } + 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 }); + } - const customFile = Bun.file(customPath); - if (await customFile.exists()) { - const content = await customFile.arrayBuffer(); - const type = customFile.type || "application/octet-stream"; - response = new Response(content, { - headers: { "Content-Type": type }, - }); this.logRequest(extendedRequest, response, ip); return response; } @@ -269,10 +269,7 @@ class ServerHandler { } } } catch (error: unknown) { - echo.error({ - message: `Error handling route ${request.url}`, - error: error, - }); + logger.error([`Error handling route ${request.url}:`, error as Error]); response = Response.json( { @@ -294,11 +291,9 @@ class ServerHandler { ); } - this.logRequest(extendedRequest, response, ip); return response; } } - const serverHandler: ServerHandler = new ServerHandler( environment.port, environment.host, diff --git a/src/views/index.html b/src/views/index.html index 1c4884f..d92aaa8 100644 --- a/src/views/index.html +++ b/src/views/index.html @@ -1,73 +1,74 @@ + + + + + - - - - - + Discord Presence - Discord Presence + + + + - - - - - - -
-
-
- -
- - - - -
- Users Time: - + +
+
-
-
-
-
-
- - - -
-
+ + + +
+ +
    +
    + + +
    + + - - -
    - -
      -
      - - - - - - - - - - \ No newline at end of file + + + diff --git a/src/websocket.ts b/src/websocket.ts index 3b00134..7b65476 100644 --- a/src/websocket.ts +++ b/src/websocket.ts @@ -1,27 +1,27 @@ -import { echo } from "@atums/echo"; +import { logger } from "@creations.works/logger"; import type { ServerWebSocket } from "bun"; class WebSocketHandler { public handleMessage(ws: ServerWebSocket, message: string): void { - echo.info(`WebSocket received: ${message}`); + logger.info(`WebSocket received: ${message}`); try { ws.send(`You said: ${message}`); } catch (error) { - echo.error({ message: "WebSocket send error", error: error }); + logger.error(["WebSocket send error", error as Error]); } } public handleOpen(ws: ServerWebSocket): void { - echo.info("WebSocket connection opened."); + logger.info("WebSocket connection opened."); try { ws.send("Welcome to the WebSocket server!"); } catch (error) { - echo.error({ message: "WebSocket send error", error: error }); + logger.error(["WebSocket send error", error as Error]); } } public handleClose(ws: ServerWebSocket, code: number, reason: string): void { - echo.warn(`WebSocket closed with code ${code}, reason: ${reason}`); + logger.warn(`WebSocket closed with code ${code}, reason: ${reason}`); } }