Compare commits

...

2 Commits

Author SHA1 Message Date
3dcf03ce53 imports
Some checks failed
Deploy Backend / deploy (push) Failing after 1m35s
Deploy Website / deploy (push) Successful in 5m38s
2024-10-16 07:48:20 +01:00
045f605cc6 switch lib 2024-10-16 07:47:52 +01:00
11 changed files with 56 additions and 48 deletions

BIN
bun.lockb

Binary file not shown.

@ -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": {
}
}

@ -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"]

@ -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",

@ -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");

@ -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(),
};
}

@ -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 /
},

@ -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
};
}
});

@ -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";

@ -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";

@ -1,11 +1,10 @@
"use client";
import { formatNumberWithCommas } from "@ssr/common/utils/number-utils";
import { formatNumberWithCommas, isWholeNumber } from "@ssr/common/utils/number-utils";
import React from "react";
import { DatasetConfig } from "@/components/chart/generic-chart";
import GenericPlayerChart from "@/components/player/chart/generic-player-chart";
import ScoreSaberPlayer from "@ssr/common/types/player/impl/scoresaber-player";
import { isWholeNumber } from "@ssr/common/utils/number-utils";
type Props = {
player: ScoreSaberPlayer;