From 3dd4d7e0d0f03cf091ea776a225455676d898825 Mon Sep 17 00:00:00 2001 From: Grant Date: Thu, 11 Jul 2024 12:05:47 -0600 Subject: [PATCH] implement canvas freezing --- .../src/components/Toolbar/UndoButton.tsx | 17 +++++++- packages/lib/src/net.ts | 9 +++- packages/server/prisma/dbml/schema.dbml | 2 + .../migration.sql | 10 +++++ packages/server/prisma/schema.prisma | 2 + packages/server/src/api/admin.ts | 43 +++++++++++++++++++ packages/server/src/lib/Canvas.ts | 31 +++++++++++++ packages/server/src/lib/Settings.ts | 11 +++++ packages/server/src/lib/SocketServer.ts | 10 +++++ packages/server/src/tools/init_settings.ts | 4 ++ 10 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 packages/server/prisma/migrations/20240711173241_add_canvas_freeze_audit_log_type/migration.sql diff --git a/packages/client/src/components/Toolbar/UndoButton.tsx b/packages/client/src/components/Toolbar/UndoButton.tsx index 284dfe2..a87b121 100644 --- a/packages/client/src/components/Toolbar/UndoButton.tsx +++ b/packages/client/src/components/Toolbar/UndoButton.tsx @@ -2,6 +2,7 @@ import { Button } from "@nextui-org/react"; import { useAppContext } from "../../contexts/AppContext"; import network from "../../lib/network"; import { useEffect, useState } from "react"; +import { toast } from "react-toastify"; export const UndoButton = () => { const { undo, config } = useAppContext(); @@ -34,7 +35,21 @@ export const UndoButton = () => { // ref-ify this? function execUndo() { network.socket.emitWithAck("undo").then((data) => { - console.log("undo", data); + if (data.success) { + console.log("Undo pixel successful"); + } else { + console.log("Undo pixel error", data); + switch (data.error) { + case "pixel_covered": + toast.error("You cannot undo a covered pixel"); + break; + case "unavailable": + toast.error("You have no undo available"); + break; + default: + toast.error("Undo error: " + data.error); + } + } }); } diff --git a/packages/lib/src/net.ts b/packages/lib/src/net.ts index 8e765cb..41e9bdc 100644 --- a/packages/lib/src/net.ts +++ b/packages/lib/src/net.ts @@ -40,6 +40,7 @@ export interface ClientToServerEvents { ack: ( _: PacketAck< Pixel, + | "canvas_frozen" | "no_user" | "invalid_pixel" | "pixel_cooldown" @@ -50,7 +51,12 @@ export interface ClientToServerEvents { ) => void ) => void; undo: ( - ack: (_: PacketAck<{}, "no_user" | "unavailable" | "pixel_covered">) => void + ack: ( + _: PacketAck< + {}, + "canvas_frozen" | "no_user" | "unavailable" | "pixel_covered" + > + ) => void ) => void; subscribe: (topic: Subscription) => void; @@ -141,6 +147,7 @@ export type PalleteColor = { export type CanvasConfig = { size: [number, number]; + frozen: boolean; zoom: number; pixel: { maxStack: number; diff --git a/packages/server/prisma/dbml/schema.dbml b/packages/server/prisma/dbml/schema.dbml index d9987aa..2336382 100644 --- a/packages/server/prisma/dbml/schema.dbml +++ b/packages/server/prisma/dbml/schema.dbml @@ -136,6 +136,8 @@ Enum AuditLogAction { BAN_DELETE CANVAS_SIZE CANVAS_FILL + CANVAS_FREEZE + CANVAS_UNFREEZE } Ref: Pixel.userId > User.sub diff --git a/packages/server/prisma/migrations/20240711173241_add_canvas_freeze_audit_log_type/migration.sql b/packages/server/prisma/migrations/20240711173241_add_canvas_freeze_audit_log_type/migration.sql new file mode 100644 index 0000000..8a050a1 --- /dev/null +++ b/packages/server/prisma/migrations/20240711173241_add_canvas_freeze_audit_log_type/migration.sql @@ -0,0 +1,10 @@ +-- AlterEnum +-- This migration adds more than one value to an enum. +-- With PostgreSQL versions 11 and earlier, this is not possible +-- in a single migration. This can be worked around by creating +-- multiple migrations, each migration adding only one value to +-- the enum. + + +ALTER TYPE "AuditLogAction" ADD VALUE 'CANVAS_FREEZE'; +ALTER TYPE "AuditLogAction" ADD VALUE 'CANVAS_UNFREEZE'; diff --git a/packages/server/prisma/schema.prisma b/packages/server/prisma/schema.prisma index 308691f..d85cce5 100644 --- a/packages/server/prisma/schema.prisma +++ b/packages/server/prisma/schema.prisma @@ -154,6 +154,8 @@ enum AuditLogAction { BAN_DELETE CANVAS_SIZE CANVAS_FILL + CANVAS_FREEZE + CANVAS_UNFREEZE } model AuditLog { diff --git a/packages/server/src/api/admin.ts b/packages/server/src/api/admin.ts index 58503b8..cd2ddac 100644 --- a/packages/server/src/api/admin.ts +++ b/packages/server/src/api/admin.ts @@ -96,6 +96,49 @@ app.post("/canvas/size", async (req, res) => { res.send({ success: true, auditLog }); }); +/** + * Get canvas frozen status + */ +app.get("/canvas/freeze", async (req, res) => { + res.send({ success: true, frozen: Canvas.frozen }); +}); + +/** + * Freeze the canvas + * + * @header X-Audit + */ +app.post("/canvas/freeze", async (req, res) => { + await Canvas.setFrozen(true); + + const user = (await User.fromAuthSession(req.session.user!))!; + const auditLog = AuditLog.Factory(user.sub) + .doing("CANVAS_FREEZE") + .reason(req.header("X-Audit") || null) + .withComment(`Freezed the canvas`) + .create(); + + res.send({ success: true, auditLog }); +}); + +/** + * Unfreeze the canvas + * + * @header X-Audit + */ +app.delete("/canvas/freeze", async (req, res) => { + await Canvas.setFrozen(false); + + const user = (await User.fromAuthSession(req.session.user!))!; + const auditLog = AuditLog.Factory(user.sub) + .doing("CANVAS_UNFREEZE") + .reason(req.header("X-Audit") || null) + .withComment(`Un-Freezed the canvas`) + .create(); + + res.send({ success: true, auditLog }); +}); + app.put("/canvas/heatmap", async (req, res) => { try { await Canvas.generateHeatmap(); diff --git a/packages/server/src/lib/Canvas.ts b/packages/server/src/lib/Canvas.ts index 6f7246b..cd42ea1 100644 --- a/packages/server/src/lib/Canvas.ts +++ b/packages/server/src/lib/Canvas.ts @@ -13,14 +13,17 @@ class Canvas { * Size of the canvas */ private canvasSize: [width: number, height: number]; + private isFrozen: boolean; constructor() { this.canvasSize = [100, 100]; + this.isFrozen = false; } getCanvasConfig(): CanvasConfig { return { size: this.canvasSize, + frozen: this.isFrozen, zoom: 7, pixel: { cooldown: 10, @@ -33,6 +36,34 @@ class Canvas { }; } + get frozen() { + return this.isFrozen; + } + + async setFrozen(frozen: boolean) { + this.isFrozen = frozen; + + await prisma.setting.upsert({ + where: { key: "canvas.frozen" }, + create: { + key: "canvas.frozen", + value: JSON.stringify(frozen), + }, + update: { + key: "canvas.frozen", + value: JSON.stringify(frozen), + }, + }); + + if (SocketServer.instance) { + SocketServer.instance.broadcastConfig(); + } else { + Logger.warn( + "[Canvas#setFrozen] SocketServer is not instantiated, cannot broadcast config" + ); + } + } + /** * Change size of the canvas * diff --git a/packages/server/src/lib/Settings.ts b/packages/server/src/lib/Settings.ts index 4d1f595..cd5be45 100644 --- a/packages/server/src/lib/Settings.ts +++ b/packages/server/src/lib/Settings.ts @@ -26,6 +26,17 @@ export const loadSettings = async (frozen = false) => { Logger.warn("Setting canvas.size is not set, did you run init_settings?"); } + // canvas frozen + const canvasFrozen = await prisma.setting.findFirst({ + where: { key: "canvas.frozen" }, + }); + if (canvasFrozen) { + const data = JSON.parse(canvasFrozen.value); + Logger.info(`Canvas frozen loaded as ${data}`); + + Canvas.setFrozen(data); + } + Logger.info( "Settings loaded into memory, waiting for side effects to finish..." ); diff --git a/packages/server/src/lib/SocketServer.ts b/packages/server/src/lib/SocketServer.ts index 6163a85..1099983 100644 --- a/packages/server/src/lib/SocketServer.ts +++ b/packages/server/src/lib/SocketServer.ts @@ -233,6 +233,11 @@ export class SocketServer { }); socket.on("place", async (pixel, bypassCooldown, ack) => { + if (getClientConfig().canvas.frozen) { + ack({ success: false, error: "canvas_frozen" }); + return; + } + if (!user) { ack({ success: false, error: "no_user" }); return; @@ -317,6 +322,11 @@ export class SocketServer { }); socket.on("undo", async (ack) => { + if (getClientConfig().canvas.frozen) { + ack({ success: false, error: "canvas_frozen" }); + return; + } + if (!user) { ack({ success: false, error: "no_user" }); return; diff --git a/packages/server/src/tools/init_settings.ts b/packages/server/src/tools/init_settings.ts index 2fe3b45..b6afbb3 100644 --- a/packages/server/src/tools/init_settings.ts +++ b/packages/server/src/tools/init_settings.ts @@ -14,6 +14,10 @@ async function main() { height: 100, }, }, + { + key: "canvas.frozen", + defaultValue: false, + }, ]; for (const setting of SETTINGS) {