Files
Dennis 50e25e3ee8 refactor: flatten monorepo structure to backend/ frontend/ devops/
Rename subdirectories for a cleaner single-repo layout:
- website-monitoring-backend/  → backend/
- website-monitoring-frontend/ → frontend/
- website-monitoring-devops/   → devops/

Update all references in package.json scripts, CI workflows,
docker-compose, pre-commit hooks, and documentation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-03-07 00:25:29 +01:00

172 lines
5.0 KiB
TypeScript

import express, { Request, Response } from "express";
import { launch } from "chrome-launcher";
import lighthouse from "lighthouse";
import cors from "cors";
const app = express();
app.use(cors());
app.use(express.json());
// Health check endpoint
app.get("/health", (req: Request, res: Response) => {
res.json({ status: "ok", timestamp: new Date().toISOString() });
});
// Enhanced lighthouse endpoint
app.post("/lighthouse", async (req: Request, res: Response) => {
const { url } = req.body;
if (!url) return res.status(400).json({ error: "Missing URL" });
console.log(`Starting Lighthouse scan for: ${url}`);
try {
// Validate URL format
try {
new URL(url);
} catch (urlError) {
return res.status(400).json({ error: "Invalid URL format" });
}
const chrome = await launch({
chromeFlags: [
"--headless",
"--no-sandbox",
"--disable-dev-shm-usage",
"--disable-gpu",
"--no-first-run",
"--disable-extensions",
"--disable-background-timer-throttling",
"--disable-backgrounding-occluded-windows",
"--disable-renderer-backgrounding",
"--disable-features=TranslateUI",
"--disable-ipc-flooding-protection",
],
});
console.log(`Chrome launched on port: ${chrome.port}`);
const runnerResult = await lighthouse(url, {
port: chrome.port,
output: "json",
logLevel: "info",
onlyCategories: ["performance", "seo", "accessibility", "best-practices"],
formFactor: "desktop",
screenEmulation: {
mobile: false,
width: 1350,
height: 940,
deviceScaleFactor: 1,
disabled: false,
},
throttling: {
rttMs: 40,
throughputKbps: 10240,
cpuSlowdownMultiplier: 1,
requestLatencyMs: 0,
downloadThroughputKbps: 0,
uploadThroughputKbps: 0,
},
});
await chrome.kill();
if (!runnerResult) throw new Error("Lighthouse returned no result");
const { categories, audits } = runnerResult.lhr;
// Extract key metrics for easier access
const metrics = {
performance: categories.performance?.score
? Math.round(categories.performance.score * 100)
: null,
seo: categories.seo?.score
? Math.round(categories.seo.score * 100)
: null,
accessibility: categories.accessibility?.score
? Math.round(categories.accessibility.score * 100)
: null,
bestPractices: categories["best-practices"]?.score
? Math.round(categories["best-practices"].score * 100)
: null,
firstContentfulPaint:
audits["first-contentful-paint"]?.numericValue || null,
largestContentfulPaint:
audits["largest-contentful-paint"]?.numericValue || null,
cumulativeLayoutShift:
audits["cumulative-layout-shift"]?.numericValue || null,
totalBlockingTime: audits["total-blocking-time"]?.numericValue || null,
speedIndex: audits["speed-index"]?.numericValue || null,
};
console.log(
`Lighthouse scan completed for: ${url} - Performance: ${metrics.performance}%`,
);
res.json({
categories,
audits,
metrics,
raw: runnerResult.lhr,
timestamp: new Date().toISOString(),
url: url,
});
} catch (error) {
console.error(`Lighthouse scan failed for ${url}:`, error);
res.status(500).json({
error: error instanceof Error ? error.message : String(error),
url: url,
timestamp: new Date().toISOString(),
});
}
});
// Batch lighthouse endpoint for multiple URLs
app.post("/lighthouse/batch", async (req: Request, res: Response) => {
const { urls } = req.body;
if (!urls || !Array.isArray(urls) || urls.length === 0) {
return res.status(400).json({ error: "Missing or empty URLs array" });
}
if (urls.length > 10) {
return res.status(400).json({ error: "Maximum 10 URLs allowed per batch" });
}
console.log(`Starting batch Lighthouse scan for ${urls.length} URLs`);
const results = [];
for (const url of urls) {
try {
// Make internal request to single lighthouse endpoint
const response = await fetch(`http://localhost:${PORT}/lighthouse`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ url }),
});
const result = await response.json();
results.push({ url, success: response.ok, data: result });
} catch (error) {
results.push({
url,
success: false,
error: error instanceof Error ? error.message : String(error),
});
}
}
res.json({
results,
total: urls.length,
successful: results.filter((r) => r.success).length,
failed: results.filter((r) => !r.success).length,
timestamp: new Date().toISOString(),
});
});
const PORT = process.env.PORT || 5001;
app.listen(PORT, () => {
console.log(`Lighthouse worker listening on http://localhost:${PORT}`);
console.log(`Health check available at http://localhost:${PORT}/health`);
});