82 lines
2.5 KiB
TypeScript
82 lines
2.5 KiB
TypeScript
import { NextRequest } from "next/server";
|
|
import { DEFAULT_PAYMENT_API_URL } from "@/config/domain.config";
|
|
import { getPaymentConfig } from "@/config/services.config";
|
|
|
|
function isPrivateHost(hostname: string): boolean {
|
|
if (!hostname) return false;
|
|
if (hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1") {
|
|
return true;
|
|
}
|
|
if (/^10\./.test(hostname) || /^192\.168\./.test(hostname)) {
|
|
return true;
|
|
}
|
|
const match = hostname.match(/^172\.(\d{1,2})\./);
|
|
if (!match) return false;
|
|
const secondOctet = Number(match[1]);
|
|
return secondOctet >= 16 && secondOctet <= 31;
|
|
}
|
|
|
|
function normalizeBaseUrl(url: string | undefined): string {
|
|
return String(url || "").trim().replace(/\/$/, "");
|
|
}
|
|
|
|
function getRequestHostname(request: NextRequest): string {
|
|
const forwardedHost = request.headers.get("x-forwarded-host");
|
|
const host = forwardedHost || request.headers.get("host") || "";
|
|
return host.split(",")[0].trim().replace(/:\d+$/, "");
|
|
}
|
|
|
|
function getMeetupApiCandidates(request: NextRequest): string[] {
|
|
const configuredApiUrl = normalizeBaseUrl(getPaymentConfig().apiUrl);
|
|
const localApiUrl = normalizeBaseUrl(DEFAULT_PAYMENT_API_URL);
|
|
const hostname = getRequestHostname(request);
|
|
const shouldPreferLocal =
|
|
process.env.NODE_ENV === "development" || isPrivateHost(hostname);
|
|
|
|
const urls = shouldPreferLocal
|
|
? [localApiUrl, configuredApiUrl]
|
|
: [configuredApiUrl, localApiUrl];
|
|
|
|
return urls.filter((url, index) => url && urls.indexOf(url) === index);
|
|
}
|
|
|
|
export async function postMeetupApi(
|
|
request: NextRequest,
|
|
path: string,
|
|
body: unknown
|
|
): Promise<{ status: number; data: unknown }> {
|
|
const apiCandidates = getMeetupApiCandidates(request);
|
|
if (apiCandidates.length === 0) {
|
|
throw new Error("PAYMENT_API_URL is not configured");
|
|
}
|
|
|
|
let lastResponse: { status: number; data: unknown } | null = null;
|
|
let lastError: unknown = null;
|
|
|
|
for (const apiUrl of apiCandidates) {
|
|
try {
|
|
const res = await fetch(`${apiUrl}${path}`, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify(body),
|
|
cache: "no-store",
|
|
});
|
|
const data = await res.json().catch(() => ({}));
|
|
|
|
if (res.ok || (res.status !== 404 && res.status < 500)) {
|
|
return { status: res.status, data };
|
|
}
|
|
|
|
lastResponse = { status: res.status, data };
|
|
} catch (error) {
|
|
lastError = error;
|
|
}
|
|
}
|
|
|
|
if (lastResponse) {
|
|
return lastResponse;
|
|
}
|
|
|
|
throw lastError instanceof Error ? lastError : new Error("Meetup API request failed");
|
|
}
|