Introduce a configuration file for all settings, remove the web-based config editor, fix the grid display, and add automatic hourly PNG exports. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 0385ea33-cde8-4bbd-8fce-8d192d30eb41 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/870d08ce-da3b-4822-9874-c2fe2b7628b1/0385ea33-cde8-4bbd-8fce-8d192d30eb41/Zffw2vY
148 lines
4.3 KiB
TypeScript
148 lines
4.3 KiB
TypeScript
import type { Express } from "express";
|
|
import { createServer, type Server } from "http";
|
|
import { WebSocketServer, WebSocket } from "ws";
|
|
import { storage } from "./storage";
|
|
import { insertPixelSchema, insertUserCooldownSchema, type WSMessage } from "@shared/schema";
|
|
import { CanvasExporter } from "./export";
|
|
|
|
export async function registerRoutes(app: Express): Promise<Server> {
|
|
const httpServer = createServer(app);
|
|
const wss = new WebSocketServer({ server: httpServer, path: '/ws' });
|
|
|
|
let connectedUsers = new Set<WebSocket>();
|
|
|
|
// Initialize canvas exporter
|
|
const exporter = new CanvasExporter(storage);
|
|
exporter.startAutoExport();
|
|
|
|
// API Routes
|
|
app.get("/api/pixels", async (req, res) => {
|
|
try {
|
|
const pixels = await storage.getAllPixels();
|
|
res.json(pixels);
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to fetch pixels" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/config", async (req, res) => {
|
|
try {
|
|
const config = await storage.getCanvasConfig();
|
|
res.json(config);
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to fetch config" });
|
|
}
|
|
});
|
|
|
|
// Config is now read-only from file
|
|
// Remove the POST endpoint for config updates
|
|
|
|
app.post("/api/pixels", async (req, res) => {
|
|
try {
|
|
const pixelData = insertPixelSchema.parse(req.body);
|
|
const config = await storage.getCanvasConfig();
|
|
|
|
// Validate coordinates
|
|
if (pixelData.x < 0 || pixelData.x >= config.canvasWidth ||
|
|
pixelData.y < 0 || pixelData.y >= config.canvasHeight) {
|
|
return res.status(400).json({ message: "Pixel coordinates out of bounds" });
|
|
}
|
|
|
|
// Check cooldown unless events are enabled
|
|
if (!config.enableAutomaticEvents) {
|
|
const cooldown = await storage.getUserCooldown(pixelData.userId);
|
|
if (cooldown && cooldown.cooldownEnds > new Date()) {
|
|
return res.status(429).json({ message: "Cooldown active" });
|
|
}
|
|
|
|
// Set new cooldown
|
|
const cooldownEnd = new Date(Date.now() + (config.defaultCooldown * 1000));
|
|
await storage.setUserCooldown({
|
|
userId: pixelData.userId,
|
|
cooldownEnds: cooldownEnd,
|
|
});
|
|
}
|
|
|
|
const pixel = await storage.placePixel(pixelData);
|
|
|
|
// Broadcast pixel placement to all connected clients
|
|
broadcast({
|
|
type: "pixel_placed",
|
|
data: {
|
|
x: pixel.x,
|
|
y: pixel.y,
|
|
color: pixel.color,
|
|
userId: pixel.userId,
|
|
username: pixel.username,
|
|
timestamp: pixel.createdAt.toISOString(),
|
|
},
|
|
});
|
|
|
|
res.json(pixel);
|
|
} catch (error) {
|
|
res.status(400).json({ message: "Invalid pixel data" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/recent", async (req, res) => {
|
|
try {
|
|
const limit = parseInt(req.query.limit as string) || 10;
|
|
const recent = await storage.getRecentPlacements(limit);
|
|
res.json(recent);
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to fetch recent placements" });
|
|
}
|
|
});
|
|
|
|
app.get("/api/cooldown/:userId", async (req, res) => {
|
|
try {
|
|
const { userId } = req.params;
|
|
const cooldown = await storage.getUserCooldown(userId);
|
|
const config = await storage.getCanvasConfig();
|
|
|
|
if (!cooldown || config.enableAutomaticEvents) {
|
|
return res.json({ remainingSeconds: 0 });
|
|
}
|
|
|
|
const remaining = Math.max(0, Math.ceil((cooldown.cooldownEnds.getTime() - Date.now()) / 1000));
|
|
res.json({ remainingSeconds: remaining });
|
|
} catch (error) {
|
|
res.status(500).json({ message: "Failed to fetch cooldown" });
|
|
}
|
|
});
|
|
|
|
// WebSocket handling
|
|
function broadcast(message: WSMessage) {
|
|
const messageStr = JSON.stringify(message);
|
|
connectedUsers.forEach(ws => {
|
|
if (ws.readyState === WebSocket.OPEN) {
|
|
ws.send(messageStr);
|
|
}
|
|
});
|
|
}
|
|
|
|
function broadcastUserCount() {
|
|
broadcast({
|
|
type: "user_count",
|
|
data: { count: connectedUsers.size },
|
|
});
|
|
}
|
|
|
|
wss.on('connection', (ws) => {
|
|
connectedUsers.add(ws);
|
|
broadcastUserCount();
|
|
|
|
ws.on('close', () => {
|
|
connectedUsers.delete(ws);
|
|
broadcastUserCount();
|
|
});
|
|
|
|
ws.on('error', () => {
|
|
connectedUsers.delete(ws);
|
|
broadcastUserCount();
|
|
});
|
|
});
|
|
|
|
return httpServer;
|
|
}
|