diff --git a/bun.lockb b/bun.lockb index 9512872..269e20d 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/projects/backend/.eslintrc.json b/projects/backend/.eslintrc.json new file mode 100644 index 0000000..6751428 --- /dev/null +++ b/projects/backend/.eslintrc.json @@ -0,0 +1,20 @@ +{ + "env": { + "browser": true, + "es2021": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "plugins": [ + "@typescript-eslint" + ], + "rules": { + } +} diff --git a/projects/backend/Dockerfile b/projects/backend/Dockerfile index 2a5ebb2..dc7fa9e 100644 --- a/projects/backend/Dockerfile +++ b/projects/backend/Dockerfile @@ -1,18 +1,5 @@ FROM oven/bun:1.1.30-alpine AS base -# Install system dependencies for node-canvas -RUN apk add --no-cache \ - build-base \ - cairo-dev \ - pango-dev \ - giflib-dev \ - libjpeg-turbo-dev \ - freetype-dev \ - fontconfig-dev \ - pixman-dev \ - python3 \ - pkgconfig - # Install dependencies FROM base AS depends WORKDIR /app @@ -37,4 +24,7 @@ RUN bun --filter '@ssr/common' build # Copy the backend project COPY --from=depends /app/projects/backend ./projects/backend +# Lint before starting +RUN bun --filter '@ssr/common' lint + CMD ["bun", "run", "--filter", "backend", "start"] diff --git a/projects/backend/package.json b/projects/backend/package.json index 45399f1..1103ec6 100644 --- a/projects/backend/package.json +++ b/projects/backend/package.json @@ -3,7 +3,8 @@ "version": "1.0.0", "scripts": { "dev": "bun run --watch src/index.ts", - "start": "bun run src/index.ts" + "start": "bun run src/index.ts", + "lint": "eslint src/**/*.ts" }, "dependencies": { "@bogeychan/elysia-etag": "^0.0.6", @@ -14,15 +15,18 @@ "@ssr/common": "workspace:common", "@tqman/nice-logger": "^1.0.1", "@typegoose/typegoose": "^12.8.0", + "@typescript-eslint/eslint-plugin": "^8.9.0", + "@typescript-eslint/parser": "^8.9.0", "@vercel/og": "^0.6.3", - "canvas": "^3.0.0-rc2", "discord-webhook-node": "^1.1.8", "elysia": "latest", "elysia-autoroutes": "^0.5.0", "elysia-decorators": "^1.0.2", "elysia-helmet": "^2.0.0", "elysia-rate-limit": "^4.1.0", + "eslint": "^8.57.1", "extract-colors": "^4.1.0", + "jimp": "^1.6.0", "ky": "^1.7.2", "mongoose": "^8.7.0", "node-cache": "^5.1.2", diff --git a/projects/backend/src/common/app-utils.ts b/projects/backend/src/common/app-utils.ts index ee8d600..1e56bb7 100644 --- a/projects/backend/src/common/app-utils.ts +++ b/projects/backend/src/common/app-utils.ts @@ -1,9 +1,9 @@ /** * Gets the app version. */ -export function getAppVersion() { +export async function getAppVersion() { if (!process.env.APP_VERSION) { - const packageJson = require("../../package.json"); + const packageJson = await import("../../package.json"); process.env.APP_VERSION = packageJson.version; } return process.env.APP_VERSION + "-" + (process.env.GIT_REV?.substring(0, 7) ?? "dev"); diff --git a/projects/backend/src/controller/app.controller.ts b/projects/backend/src/controller/app.controller.ts index a636562..94c4932 100644 --- a/projects/backend/src/controller/app.controller.ts +++ b/projects/backend/src/controller/app.controller.ts @@ -5,10 +5,10 @@ import { AppService } from "../service/app.service"; @Controller() export default class AppController { @Get("/") - public index() { + public async index() { return { app: "backend", - version: getAppVersion(), + version: await getAppVersion(), }; } diff --git a/projects/backend/src/index.ts b/projects/backend/src/index.ts index 6c711f7..3840d21 100644 --- a/projects/backend/src/index.ts +++ b/projects/backend/src/index.ts @@ -21,9 +21,6 @@ import { delay } from "@ssr/common/utils/utils"; import { connectScoreSaberWebSocket } from "@ssr/common/websocket/scoresaber-websocket"; import ImageController from "./controller/image.controller"; import ReplayController from "./controller/replay.controller"; -// @ts-ignore -import { MessageBuilder, Webhook } from "discord-webhook-node"; -import { formatPp } from "@ssr/common/utils/number-utils"; import { ScoreService } from "./service/score.service"; // Load .env file @@ -98,7 +95,7 @@ app.onError({ as: "global" }, ({ code, error }) => { return error.all; } - let status = "status" in error ? error.status : undefined; + const status = "status" in error ? error.status : undefined; return { ...((status && { statusCode: status }) || { status: code }), ...(error.message != code && { message: error.message }), @@ -134,7 +131,10 @@ app.use( duration: 60 * 1000, max: 100, skip: request => { + // Skip requests to / + // eslint-disable-next-line @typescript-eslint/no-unused-vars,prefer-const let [_, path] = request.url.split("/"); // Get the url parts + // eslint-disable-next-line @typescript-eslint/no-unused-expressions path === "" || (path === undefined && (path = "/")); // If we're on /, the path is undefined, so we set it to / return path === "/"; // ignore all requests to / }, diff --git a/projects/backend/src/service/image.service.tsx b/projects/backend/src/service/image.service.tsx index 24a96a6..9249386 100644 --- a/projects/backend/src/service/image.service.tsx +++ b/projects/backend/src/service/image.service.tsx @@ -9,8 +9,7 @@ import NodeCache from "node-cache"; import ScoreSaberLeaderboardToken from "@ssr/common/types/token/scoresaber/score-saber-leaderboard-token"; import ScoreSaberPlayer, { getScoreSaberPlayerFromToken } from "@ssr/common/types/player/impl/scoresaber-player"; import { Config } from "../common/config"; -import ky from "ky"; -import { createCanvas, loadImage } from "canvas"; +import { Jimp } from "jimp"; import { extractColors } from "extract-colors"; const cache = new NodeCache({ stdTTL: 60 * 60, checkperiod: 120 }); @@ -29,33 +28,27 @@ export class ImageService { return await this.fetchWithCache<{ color: string }>(`average_color-${src}`, async () => { try { - const response = await ky.get(src); - if (response.status !== 200) { - throw new Error(`Failed to fetch image: ${src}`); + const image = await Jimp.read(src); // Load image using Jimp + const { width, height, data } = image.bitmap; // Access image dimensions and pixel data + + // Convert the Buffer data to Uint8ClampedArray + const uint8ClampedArray = new Uint8ClampedArray(data); + + // Extract the colors using extract-colors + const colors = await extractColors({ data: uint8ClampedArray, width, height }); + + // Return the most dominant color, or fallback if none found + if (colors && colors.length > 0) { + return { color: colors[2].hex }; // Returning the third most dominant color } - const imageBuffer = await response.arrayBuffer(); - - // Create an image from the buffer using canvas - const img = await loadImage(Buffer.from(imageBuffer)); - const canvas = createCanvas(img.width, img.height); - const ctx = canvas.getContext("2d"); - - // Draw the image onto the canvas - ctx.drawImage(img, 0, 0); - - // Get the pixel data from the canvas - const imageData = ctx.getImageData(0, 0, img.width, img.height); - const { data, width, height } = imageData; - - // Extract the colors - const color = await extractColors({ data, width, height }); return { - color: color[2].hex, + color: "#fff", // Fallback color in case no colors are found }; } catch (error) { + console.error("Error fetching image or extracting colors:", error); return { - color: "#fff", + color: "#fff", // Fallback color in case of an error }; } }); diff --git a/projects/backend/src/service/player.service.ts b/projects/backend/src/service/player.service.ts index f0f04db..6af0ea8 100644 --- a/projects/backend/src/service/player.service.ts +++ b/projects/backend/src/service/player.service.ts @@ -5,6 +5,7 @@ import { scoresaberService } from "@ssr/common/service/impl/scoresaber"; import ScoreSaberPlayerToken from "@ssr/common/types/token/scoresaber/score-saber-player-token"; import { InternalServerError } from "../error/internal-server-error"; import ScoreSaberPlayerScoreToken from "@ssr/common/types/token/scoresaber/score-saber-player-score-token"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { MessageBuilder, Webhook } from "discord-webhook-node"; import { Config } from "../common/config"; diff --git a/projects/backend/src/service/score.service.ts b/projects/backend/src/service/score.service.ts index ce6fbba..4033b2f 100644 --- a/projects/backend/src/service/score.service.ts +++ b/projects/backend/src/service/score.service.ts @@ -1,4 +1,5 @@ import ScoreSaberPlayerScoreToken from "@ssr/common/types/token/scoresaber/score-saber-player-score-token"; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { MessageBuilder, Webhook } from "discord-webhook-node"; import { Config } from "../common/config";