Commit e86d36fa authored by Grant's avatar Grant
Browse files

Draft: pixel countdown

parent 0e554489
Loading
Loading
Loading
Loading
+847 −0

File changed.

Preview size limit exceeded, changes collapsed.

+1 −0
Original line number Diff line number Diff line
@@ -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",
+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";
@@ -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();
+1 −1
Original line number Diff line number Diff line
@@ -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),
+56 −4
Original line number Diff line number Diff line
@@ -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");
    }
@@ -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") {
@@ -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
   *
@@ -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