feat: initial codebase v0.4.1
Some checks failed
Deploy to Staging / build-and-deploy (push) Failing after 9m44s
Some checks failed
Deploy to Staging / build-and-deploy (push) Failing after 9m44s
- Extract complete codebase from running staging pod - Add Dockerfile with multi-stage build for Node.js + Puppeteer - Configure CI/CD workflows for staging and production deployment - Include all source files, configs, and public assets
This commit is contained in:
commit
b58f634318
28 changed files with 5669 additions and 0 deletions
68
src/routes/playground.ts
Normal file
68
src/routes/playground.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import { Router } from "express";
|
||||
import { takeScreenshot } from "../services/screenshot.js";
|
||||
import { addWatermark } from "../services/watermark.js";
|
||||
import logger from "../services/logger.js";
|
||||
import rateLimit from "express-rate-limit";
|
||||
|
||||
export const playgroundRouter = Router();
|
||||
|
||||
// 5 requests per hour per IP
|
||||
const playgroundLimiter = rateLimit({
|
||||
windowMs: 60 * 60 * 1000,
|
||||
max: 5,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { error: "Playground rate limit exceeded (5 requests/hour). Get an API key for unlimited access.", upgrade: "https://snapapi.eu/#pricing" },
|
||||
keyGenerator: (req) => req.ip || req.socket.remoteAddress || "unknown",
|
||||
});
|
||||
|
||||
playgroundRouter.post("/", playgroundLimiter, async (req, res) => {
|
||||
const { url, format, width, height } = req.body;
|
||||
|
||||
if (!url || typeof url !== "string") {
|
||||
res.status(400).json({ error: "Missing required parameter: url" });
|
||||
return;
|
||||
}
|
||||
|
||||
// Enforce reasonable limits for playground
|
||||
const safeWidth = Math.min(Math.max(parseInt(width, 10) || 1280, 320), 1920);
|
||||
const safeHeight = Math.min(Math.max(parseInt(height, 10) || 800, 200), 1080);
|
||||
const safeFormat = ["png", "jpeg", "webp"].includes(format) ? format : "png";
|
||||
|
||||
try {
|
||||
const result = await takeScreenshot({
|
||||
url,
|
||||
format: safeFormat as "png" | "jpeg" | "webp",
|
||||
width: safeWidth,
|
||||
height: safeHeight,
|
||||
fullPage: false,
|
||||
quality: safeFormat === "png" ? undefined : 70,
|
||||
deviceScale: 1,
|
||||
});
|
||||
|
||||
// Add watermark
|
||||
const watermarked = await addWatermark(result.buffer, safeWidth, safeHeight);
|
||||
|
||||
res.setHeader("Content-Type", result.contentType);
|
||||
res.setHeader("Content-Length", watermarked.length);
|
||||
res.setHeader("Cache-Control", "no-store");
|
||||
res.setHeader("X-Playground", "true");
|
||||
res.send(watermarked);
|
||||
} catch (err: any) {
|
||||
logger.error({ err: err.message, url }, "Playground screenshot failed");
|
||||
|
||||
if (err.message === "QUEUE_FULL") {
|
||||
res.status(503).json({ error: "Service busy. Try again shortly." });
|
||||
return;
|
||||
}
|
||||
if (err.message === "SCREENSHOT_TIMEOUT") {
|
||||
res.status(504).json({ error: "Screenshot timed out." });
|
||||
return;
|
||||
}
|
||||
if (err.message.includes("blocked") || err.message.includes("not allowed") || err.message.includes("Invalid URL")) {
|
||||
res.status(400).json({ error: err.message });
|
||||
return;
|
||||
}
|
||||
res.status(500).json({ error: "Screenshot failed" });
|
||||
}
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue