Loading package-lock.json +847 −0 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/server/package.json +1 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ "express-rate-limit": "^7.5.0", "express-session": "^1.18.1", "ioredis": "^5.6.1", "jimp": "^1.6.0", "openid-client": "^6.1.7", "prom-client": "^15.1.3", "rate-limit-redis": "^4.2.0", Loading packages/server/src/api/admin/canvas.ts +40 −0 Original line number Diff line number Diff line import { Jimp } from "jimp"; import { z } from "zod/v4"; import { CanvasController } from "../../controllers/CanvasController"; import { SocketController } from "../../controllers/SocketController"; import { getLogger } from "../../lib/Logger"; Loading @@ -8,7 +11,44 @@ import { Router } from "../lib/router"; const Logger = getLogger("HTTP/ADMIN"); const LoadImage = z.object({ image: z.string(), user: z.string(), }); type LoadImage = z.infer<typeof LoadImage>; const pad = (str: string, len: number, spacer = "0") => { return spacer.repeat(Math.max(0, len - str.length)) + str; }; export class CanvasAdminEndpoints extends Router { @Router.handler("post", "/load-image") @Router.body(LoadImage) async loadImage(req: Router.Request<LoadImage>, res: Router.Response) { const Canvas = CanvasController.get(); const [width, height] = Canvas.getSize(); await User.upsertSystemAccount({ display_name: req.body.user }); const image = await Jimp.read(req.body.image); const pixels: string[][] = []; for (let x = 0; x < width; x++) { pixels[x] = []; for (let y = 0; y < height; y++) { const color = pad(image.getPixelColor(x, y).toString(16), 8).slice( 0, 6 ); pixels[x][y] = color; } } await Canvas.setPixelBatch({ sub: User.SYSTEM_SUB }, pixels); res.send("ok"); } @Router.handler("get", "/size") async getSize(req: Router.Request, res: Router.Response) { const [width, height] = CanvasController.get().getSize(); Loading packages/server/src/api/lib/router.ts +1 −1 Original line number Diff line number Diff line Loading @@ -151,7 +151,7 @@ export class Router { if (handleError) { handleError(req, res); } else { res.json({ res.status(400).json({ success: false, error: "ValidationError", error_message: z.prettifyError(err), Loading packages/server/src/controllers/CanvasController.ts +56 −4 Original line number Diff line number Diff line Loading @@ -35,7 +35,7 @@ export class CanvasController { * @setting canvas.size * @setting canvas.frozen */ static async initialize() { static async initialize(skipWrite?: boolean) { if (typeof CanvasController.instance !== "undefined") { throw new Error("CanvasController#initialize when initialized"); } Loading @@ -50,11 +50,13 @@ export class CanvasController { const canvasFrozen = Settings.get("canvas.frozen"); instance.isFrozen = canvasFrozen; if (!skipWrite) { // run sideeffects await instance.canvasToRedis(); ClientConfigService.set("canvas", instance.buildCanvasConfig()); } } static get(): CanvasController { if (typeof CanvasController.instance === "undefined") { Loading Loading @@ -278,6 +280,26 @@ export class CanvasController { return coveringPixel; } /** * "Delete" a batch of pixels * * Does not update the covered pixels and will leave holes * * This is intended for the countdown were the only pixels that exist are the previous image */ async deletePixelBatch(pixels: Pixel[]) { await prisma.pixel.updateMany({ where: { id: { in: pixels.map((p) => p.id) }, isTop: true, }, data: { deletedAt: new Date(), isTop: false, }, }); } /** * Chunks canvas pixels and caches chunks in redis * Loading Loading @@ -499,6 +521,36 @@ export class CanvasController { // }); } /** * * @param user * @param pixels array of hex values full size of canvas */ async setPixelBatch(user: { sub: string }, pixels: string[][]) { await prisma.pixel.updateMany({ where: { isTop: true }, data: { isTop: false }, }); await prisma.pixel.createMany({ data: pixels .map((row, x) => row.map((hex, y) => ({ userId: user.sub, color: hex, x, y, isTop: true, isModAction: true, })) ) .flat(), }); await this.canvasToRedis(); Logger.info("setPixelBatch finished"); } /** * Force a pixel to be updated in redis * @param x Loading Loading
packages/server/package.json +1 −0 Original line number Diff line number Diff line Loading @@ -57,6 +57,7 @@ "express-rate-limit": "^7.5.0", "express-session": "^1.18.1", "ioredis": "^5.6.1", "jimp": "^1.6.0", "openid-client": "^6.1.7", "prom-client": "^15.1.3", "rate-limit-redis": "^4.2.0", Loading
packages/server/src/api/admin/canvas.ts +40 −0 Original line number Diff line number Diff line import { Jimp } from "jimp"; import { z } from "zod/v4"; import { CanvasController } from "../../controllers/CanvasController"; import { SocketController } from "../../controllers/SocketController"; import { getLogger } from "../../lib/Logger"; Loading @@ -8,7 +11,44 @@ import { Router } from "../lib/router"; const Logger = getLogger("HTTP/ADMIN"); const LoadImage = z.object({ image: z.string(), user: z.string(), }); type LoadImage = z.infer<typeof LoadImage>; const pad = (str: string, len: number, spacer = "0") => { return spacer.repeat(Math.max(0, len - str.length)) + str; }; export class CanvasAdminEndpoints extends Router { @Router.handler("post", "/load-image") @Router.body(LoadImage) async loadImage(req: Router.Request<LoadImage>, res: Router.Response) { const Canvas = CanvasController.get(); const [width, height] = Canvas.getSize(); await User.upsertSystemAccount({ display_name: req.body.user }); const image = await Jimp.read(req.body.image); const pixels: string[][] = []; for (let x = 0; x < width; x++) { pixels[x] = []; for (let y = 0; y < height; y++) { const color = pad(image.getPixelColor(x, y).toString(16), 8).slice( 0, 6 ); pixels[x][y] = color; } } await Canvas.setPixelBatch({ sub: User.SYSTEM_SUB }, pixels); res.send("ok"); } @Router.handler("get", "/size") async getSize(req: Router.Request, res: Router.Response) { const [width, height] = CanvasController.get().getSize(); Loading
packages/server/src/api/lib/router.ts +1 −1 Original line number Diff line number Diff line Loading @@ -151,7 +151,7 @@ export class Router { if (handleError) { handleError(req, res); } else { res.json({ res.status(400).json({ success: false, error: "ValidationError", error_message: z.prettifyError(err), Loading
packages/server/src/controllers/CanvasController.ts +56 −4 Original line number Diff line number Diff line Loading @@ -35,7 +35,7 @@ export class CanvasController { * @setting canvas.size * @setting canvas.frozen */ static async initialize() { static async initialize(skipWrite?: boolean) { if (typeof CanvasController.instance !== "undefined") { throw new Error("CanvasController#initialize when initialized"); } Loading @@ -50,11 +50,13 @@ export class CanvasController { const canvasFrozen = Settings.get("canvas.frozen"); instance.isFrozen = canvasFrozen; if (!skipWrite) { // run sideeffects await instance.canvasToRedis(); ClientConfigService.set("canvas", instance.buildCanvasConfig()); } } static get(): CanvasController { if (typeof CanvasController.instance === "undefined") { Loading Loading @@ -278,6 +280,26 @@ export class CanvasController { return coveringPixel; } /** * "Delete" a batch of pixels * * Does not update the covered pixels and will leave holes * * This is intended for the countdown were the only pixels that exist are the previous image */ async deletePixelBatch(pixels: Pixel[]) { await prisma.pixel.updateMany({ where: { id: { in: pixels.map((p) => p.id) }, isTop: true, }, data: { deletedAt: new Date(), isTop: false, }, }); } /** * Chunks canvas pixels and caches chunks in redis * Loading Loading @@ -499,6 +521,36 @@ export class CanvasController { // }); } /** * * @param user * @param pixels array of hex values full size of canvas */ async setPixelBatch(user: { sub: string }, pixels: string[][]) { await prisma.pixel.updateMany({ where: { isTop: true }, data: { isTop: false }, }); await prisma.pixel.createMany({ data: pixels .map((row, x) => row.map((hex, y) => ({ userId: user.sub, color: hex, x, y, isTop: true, isModAction: true, })) ) .flat(), }); await this.canvasToRedis(); Logger.info("setPixelBatch finished"); } /** * Force a pixel to be updated in redis * @param x Loading