Compare commits

..

1 Commits

Author SHA1 Message Date
Zimeng Xiong 0e8bec949b update nginx config 2025-11-22 21:06:01 -08:00
3 changed files with 54 additions and 46 deletions
+39 -28
View File
@@ -5,7 +5,6 @@ import path from "path";
import fs from "fs"; import fs from "fs";
import { createServer } from "http"; import { createServer } from "http";
import { Server } from "socket.io"; import { Server } from "socket.io";
import { Worker } from "worker_threads";
import multer from "multer"; import multer from "multer";
import archiver from "archiver"; import archiver from "archiver";
import Database from "better-sqlite3"; import Database from "better-sqlite3";
@@ -78,7 +77,13 @@ const prisma = new PrismaClient();
const PORT = process.env.PORT || 8000; const PORT = process.env.PORT || 8000;
// Multer setup for file uploads // Multer setup for file uploads
const upload = multer({ dest: uploadDir }); const upload = multer({
dest: uploadDir,
limits: {
fileSize: 50 * 1024 * 1024, // 50MB in bytes
files: 1, // Only one file per upload
},
});
app.use( app.use(
cors({ cors({
@@ -89,6 +94,22 @@ app.use(
app.use(express.json({ limit: "50mb" })); app.use(express.json({ limit: "50mb" }));
app.use(express.urlencoded({ extended: true, limit: "50mb" })); app.use(express.urlencoded({ extended: true, limit: "50mb" }));
// Log large requests for monitoring and debugging
app.use((req, res, next) => {
const contentLength = req.headers["content-length"];
if (contentLength) {
const sizeInMB = parseInt(contentLength) / 1024 / 1024;
if (sizeInMB > 10) {
console.log(
`[LARGE REQUEST] ${req.method} ${req.path} - ${sizeInMB.toFixed(
2
)}MB - Content-Length: ${contentLength} bytes`
);
}
}
next();
});
const elementsSchema = z.array(z.object({}).passthrough()); const elementsSchema = z.array(z.object({}).passthrough());
const appStateSchema = z.object({}).passthrough(); const appStateSchema = z.object({}).passthrough();
@@ -126,31 +147,21 @@ const respondWithValidationErrors = (
}); });
}; };
// Non-blocking CPU check using worker threads const runIntegrityCheck = (filePath: string): boolean => {
const verifyDatabaseIntegrityAsync = (filePath: string): Promise<boolean> => { let dbInstance: Database.Database | undefined;
return new Promise((resolve) => { try {
const worker = new Worker( dbInstance = new Database(filePath, {
path.resolve(__dirname, "./workers/db-verify.js"), readonly: true,
{ fileMustExist: true,
workerData: { filePath },
}
);
worker.on("message", (isValid: boolean) => resolve(isValid));
worker.on("error", (err) => {
console.error("Worker error:", err);
resolve(false);
}); });
worker.on("exit", (code) => { const result = dbInstance.prepare("PRAGMA integrity_check;").get();
if (code !== 0) resolve(false); return result?.integrity_check === "ok";
}); } catch (error) {
console.error("Integrity check failed:", error);
// Kill worker if it takes too long (DoS protection) return false;
setTimeout(() => { } finally {
worker.terminate(); dbInstance?.close();
resolve(false); }
}, 10000); // 10 second timeout
});
}; };
const removeFileIfExists = (filePath?: string) => { const removeFileIfExists = (filePath?: string) => {
@@ -656,7 +667,7 @@ app.post("/import/sqlite/verify", upload.single("db"), async (req, res) => {
} }
const stagedPath = req.file.path; const stagedPath = req.file.path;
const isValid = await verifyDatabaseIntegrityAsync(stagedPath); const isValid = runIntegrityCheck(stagedPath);
removeFileIfExists(stagedPath); removeFileIfExists(stagedPath);
if (!isValid) { if (!isValid) {
@@ -695,7 +706,7 @@ app.post("/import/sqlite", upload.single("db"), async (req, res) => {
return res.status(500).json({ error: "Failed to stage uploaded file" }); return res.status(500).json({ error: "Failed to stage uploaded file" });
} }
const isValid = await verifyDatabaseIntegrityAsync(stagedPath); const isValid = runIntegrityCheck(stagedPath);
if (!isValid) { if (!isValid) {
removeFileIfExists(stagedPath); removeFileIfExists(stagedPath);
return res return res
-18
View File
@@ -1,18 +0,0 @@
const { parentPort, workerData } = require('worker_threads');
const Database = require('better-sqlite3');
if (!parentPort) throw new Error("Must be run in a worker thread");
try {
const { filePath } = workerData;
const db = new Database(filePath, { readonly: true, fileMustExist: true });
// This is the CPU-heavy operation
const result = db.prepare("PRAGMA integrity_check;").get();
db.close();
parentPort.postMessage(result.integrity_check === "ok");
} catch (error) {
// Any error means invalid or corrupt DB
parentPort.postMessage(false);
}
+15
View File
@@ -12,6 +12,9 @@ http {
gzip_vary on; gzip_vary on;
gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;
# Set maximum request body size to 50MB to handle large drawings with embedded images
client_max_body_size 50M;
server { server {
listen 80; listen 80;
server_name localhost; server_name localhost;
@@ -29,6 +32,18 @@ http {
proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Proto $scheme;
# Buffer and timeout settings for large payloads
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
proxy_busy_buffers_size 8k;
client_body_buffer_size 128k;
# Timeouts for large uploads (300 seconds)
proxy_connect_timeout 300s;
proxy_send_timeout 300s;
proxy_read_timeout 300s;
} }
# WebSocket proxy for Socket.IO # WebSocket proxy for Socket.IO