Some checks failed
Dev Deployment (Zero Downtime) / deploy-dev (push) Failing after 7m46s
- Updated deployment script to check for existing containers and free ports before starting a new container. - Added a new script to check the status of the Gitea runner, including service checks, running processes, Docker containers, common directories, and network connections.
134 lines
4.6 KiB
TypeScript
134 lines
4.6 KiB
TypeScript
// app/api/n8n/status/route.ts
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
|
|
// Cache für 30 Sekunden, damit wir n8n nicht zuspammen
|
|
export const revalidate = 30;
|
|
|
|
export async function GET(request: NextRequest) {
|
|
// Rate limiting for n8n status endpoint
|
|
const ip =
|
|
request.headers.get("x-forwarded-for") ||
|
|
request.headers.get("x-real-ip") ||
|
|
"unknown";
|
|
const ua = request.headers.get("user-agent") || "unknown";
|
|
const { checkRateLimit } = await import('@/lib/auth');
|
|
|
|
// In dev, many requests can share ip=unknown; use UA to avoid a shared bucket.
|
|
const rateKey =
|
|
process.env.NODE_ENV === "development" && ip === "unknown"
|
|
? `ua:${ua.slice(0, 120)}`
|
|
: ip;
|
|
const maxPerMinute = process.env.NODE_ENV === "development" ? 300 : 30;
|
|
|
|
if (!checkRateLimit(rateKey, maxPerMinute, 60000)) { // requests per minute
|
|
return NextResponse.json(
|
|
{ error: 'Rate limit exceeded. Please try again later.' },
|
|
{ status: 429 }
|
|
);
|
|
}
|
|
try {
|
|
// Check if n8n webhook URL is configured
|
|
const n8nWebhookUrl = process.env.N8N_WEBHOOK_URL;
|
|
|
|
if (!n8nWebhookUrl) {
|
|
console.warn("N8N_WEBHOOK_URL not configured for status endpoint");
|
|
// Return fallback if n8n is not configured
|
|
return NextResponse.json({
|
|
status: { text: "offline", color: "gray" },
|
|
music: null,
|
|
gaming: null,
|
|
coding: null,
|
|
});
|
|
}
|
|
|
|
// Rufe den n8n Webhook auf
|
|
// Add timestamp to query to bypass Cloudflare cache
|
|
const statusUrl = `${n8nWebhookUrl}/webhook/denshooter-71242/status?t=${Date.now()}`;
|
|
console.log(`Fetching status from: ${statusUrl}`);
|
|
|
|
// Add timeout to prevent hanging requests
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
|
|
|
|
try {
|
|
const res = await fetch(statusUrl, {
|
|
method: "GET",
|
|
headers: {
|
|
// n8n sometimes responds with empty body; we'll parse defensively below.
|
|
Accept: "application/json",
|
|
...(process.env.N8N_SECRET_TOKEN && {
|
|
Authorization: `Bearer ${process.env.N8N_SECRET_TOKEN}`,
|
|
}),
|
|
},
|
|
next: { revalidate: 30 },
|
|
signal: controller.signal,
|
|
});
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
if (!res.ok) {
|
|
const errorText = await res.text().catch(() => 'Unknown error');
|
|
console.error(`n8n status webhook failed: ${res.status}`, errorText);
|
|
throw new Error(`n8n error: ${res.status} - ${errorText}`);
|
|
}
|
|
|
|
const raw = await res.text().catch(() => "");
|
|
if (!raw || !raw.trim()) {
|
|
throw new Error("Empty response body received from n8n");
|
|
}
|
|
|
|
let data: unknown;
|
|
try {
|
|
data = JSON.parse(raw);
|
|
} catch (_parseError) {
|
|
// Sometimes upstream sends HTML or a partial response; include a snippet for debugging.
|
|
const snippet = raw.slice(0, 240);
|
|
throw new Error(
|
|
`Invalid JSON from n8n (${res.status}): ${snippet}${raw.length > 240 ? "…" : ""}`,
|
|
);
|
|
}
|
|
|
|
// n8n gibt oft ein Array zurück: [{...}]. Wir wollen nur das Objekt.
|
|
const statusData = Array.isArray(data) ? data[0] : data;
|
|
|
|
// Safety check: if statusData is still undefined/null (e.g. empty array), use fallback
|
|
if (!statusData) {
|
|
throw new Error("Empty data received from n8n");
|
|
}
|
|
|
|
// Ensure coding object has proper structure
|
|
if (statusData.coding && typeof statusData.coding === "object") {
|
|
// Already properly formatted from n8n
|
|
} else if (statusData.coding === null || statusData.coding === undefined) {
|
|
// No coding data - keep as null
|
|
statusData.coding = null;
|
|
}
|
|
|
|
return NextResponse.json(statusData);
|
|
} catch (fetchError: unknown) {
|
|
clearTimeout(timeoutId);
|
|
|
|
if (fetchError instanceof Error && fetchError.name === 'AbortError') {
|
|
console.error("n8n status webhook request timed out");
|
|
} else {
|
|
console.error("n8n status webhook fetch error:", fetchError);
|
|
}
|
|
throw fetchError;
|
|
}
|
|
} catch (error: unknown) {
|
|
console.error("Error fetching n8n status:", error);
|
|
console.error("Error details:", {
|
|
message: error instanceof Error ? error.message : String(error),
|
|
stack: error instanceof Error ? error.stack : undefined,
|
|
n8nUrl: process.env.N8N_WEBHOOK_URL ? 'configured' : 'missing',
|
|
});
|
|
// Leeres Fallback-Objekt, damit die Seite nicht abstürzt
|
|
return NextResponse.json({
|
|
status: { text: "offline", color: "gray" },
|
|
music: null,
|
|
gaming: null,
|
|
coding: null,
|
|
});
|
|
}
|
|
}
|