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>
This commit is contained in:
Dennis
2026-03-07 00:25:29 +01:00
parent 4607af8def
commit 50e25e3ee8
253 changed files with 54 additions and 51 deletions
+21
View File
@@ -0,0 +1,21 @@
FROM node:20-slim
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm install
# Install Chromium for Lighthouse
RUN apt-get update && \
apt-get install -y wget ca-certificates fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libcups2 libdbus-1-3 libgdk-pixbuf2.0-0 libnspr4 libnss3 libx11-xcb1 libxcomposite1 libxdamage1 libxrandr2 xdg-utils chromium && \
ln -s /usr/bin/chromium /usr/bin/chromium-browser
COPY . .
RUN npm install -g typescript
RUN tsc
EXPOSE 5001
CMD ["node", "dist/express-worker.js"]
+171
View File
@@ -0,0 +1,171 @@
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`);
});
File diff suppressed because it is too large Load Diff
+23
View File
@@ -0,0 +1,23 @@
{
"name": "scanner-worker",
"version": "1.0.0",
"main": "express-worker.ts",
"scripts": {
"start": "ts-node express-worker.ts"
},
"dependencies": {
"@supabase/supabase-js": "^2.50.0",
"chrome-launcher": "^0.15.0",
"cors": "^2.8.5",
"dotenv": "^16.5.0",
"express": "^4.18.2",
"lighthouse": "^12.6.1"
},
"devDependencies": {
"@types/cors": "^2.8.19",
"@types/express": "^4.17.21",
"@types/node": "^20.0.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.0"
}
}
+94
View File
@@ -0,0 +1,94 @@
import { createClient } from "@supabase/supabase-js";
import { launch } from "chrome-launcher";
import lighthouse from "lighthouse";
import dotenv from "dotenv";
dotenv.config();
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_KEY!,
);
async function main() {
// 1. Hole alle offenen Scans
const { data: scans, error: scanError } = await supabase
.from("scans")
.select("id, page_id")
.eq("status", "pending");
if (scanError) {
console.error("Fehler beim Laden der Scans:", scanError);
process.exit(1);
}
for (const scan of scans ?? []) {
try {
// 2. Hole die URL der Seite
const { data: page, error: pageError } = await supabase
.from("pages")
.select("url")
.eq("id", scan.page_id)
.single();
if (pageError || !page) {
console.error("Fehler beim Laden der Seite:", pageError);
continue;
}
// 3. Setze Scan-Status auf "running"
await supabase
.from("scans")
.update({ status: "running" })
.eq("id", scan.id);
// 4. Starte Lighthouse
const chrome = await launch({
chromeFlags: ["--headless", "--no-sandbox", "--disable-dev-shm-usage"],
});
const runnerResult = await lighthouse(page.url, {
port: chrome.port,
output: "json",
logLevel: "info",
onlyCategories: [
"performance",
"seo",
"accessibility",
"best-practices",
],
});
await chrome.kill();
if (!runnerResult) throw new Error("Lighthouse returned no result");
// 5. Speichere Ergebnisse
await supabase.from("scan_results").insert([
{
scan_id: scan.id,
raw_data: runnerResult.lhr,
},
]);
// 6. Setze Scan-Status auf "completed"
await supabase
.from("scans")
.update({ status: "completed" })
.eq("id", scan.id);
console.log(`Scan für ${page.url} abgeschlossen.`);
} catch (error) {
console.error("Scan-Fehler:", error);
await supabase
.from("scans")
.update({
status: "failed",
error_message: error instanceof Error ? error.message : String(error),
})
.eq("id", scan.id);
}
}
}
main();
+11
View File
@@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "CommonJS",
"outDir": "dist",
"esModuleInterop": true,
"strict": true,
"skipLibCheck": true
},
"include": ["*.ts"]
}