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.
132 lines
4.6 KiB
TypeScript
132 lines
4.6 KiB
TypeScript
// app/api/n8n/hardcover/currently-reading/route.ts
|
|
import { NextRequest, NextResponse } from "next/server";
|
|
|
|
// Cache für 5 Minuten, damit wir n8n nicht zuspammen
|
|
// Hardcover-Daten ändern sich nicht so häufig
|
|
export const revalidate = 300;
|
|
|
|
export async function GET(request: NextRequest) {
|
|
// Rate limiting for n8n hardcover 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" ? 60 : 10;
|
|
|
|
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 hardcover endpoint");
|
|
// Return fallback if n8n is not configured
|
|
return NextResponse.json({
|
|
currentlyReading: null,
|
|
});
|
|
}
|
|
|
|
// Rufe den n8n Webhook auf
|
|
// Add timestamp to query to bypass Cloudflare cache
|
|
const webhookUrl = `${n8nWebhookUrl}/webhook/hardcover/currently-reading?t=${Date.now()}`;
|
|
console.log(`Fetching currently reading from: ${webhookUrl}`);
|
|
|
|
// Add timeout to prevent hanging requests
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10 second timeout
|
|
|
|
try {
|
|
const res = await fetch(webhookUrl, {
|
|
method: "GET",
|
|
headers: {
|
|
Accept: "application/json",
|
|
...(process.env.N8N_SECRET_TOKEN && {
|
|
Authorization: `Bearer ${process.env.N8N_SECRET_TOKEN}`,
|
|
}),
|
|
...(process.env.N8N_API_KEY && {
|
|
"X-API-Key": process.env.N8N_API_KEY,
|
|
}),
|
|
},
|
|
next: { revalidate: 300 },
|
|
signal: controller.signal,
|
|
});
|
|
|
|
clearTimeout(timeoutId);
|
|
|
|
if (!res.ok) {
|
|
const errorText = await res.text().catch(() => 'Unknown error');
|
|
console.error(`n8n hardcover 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 readingData = Array.isArray(data) ? data[0] : data;
|
|
|
|
// Safety check: if readingData is still undefined/null (e.g. empty array), use fallback
|
|
if (!readingData) {
|
|
throw new Error("Empty data received from n8n");
|
|
}
|
|
|
|
// Ensure currentlyReading has proper structure
|
|
if (readingData.currentlyReading && typeof readingData.currentlyReading === "object") {
|
|
// Already properly formatted from n8n
|
|
} else if (readingData.currentlyReading === null || readingData.currentlyReading === undefined) {
|
|
// No reading data - keep as null
|
|
readingData.currentlyReading = null;
|
|
}
|
|
|
|
return NextResponse.json(readingData);
|
|
} catch (fetchError: unknown) {
|
|
clearTimeout(timeoutId);
|
|
|
|
if (fetchError instanceof Error && fetchError.name === 'AbortError') {
|
|
console.error("n8n hardcover webhook request timed out");
|
|
} else {
|
|
console.error("n8n hardcover webhook fetch error:", fetchError);
|
|
}
|
|
throw fetchError;
|
|
}
|
|
} catch (error: unknown) {
|
|
console.error("Error fetching n8n hardcover data:", 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({
|
|
currentlyReading: null,
|
|
});
|
|
}
|
|
}
|