139 lines
4.5 KiB
TypeScript
139 lines
4.5 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { fetchBackendAccessToken, requireAdminSession } from '../../_utils/backendAuth';
|
|
|
|
export const runtime = 'nodejs';
|
|
|
|
function resolvePreferencesBackendPath(): string {
|
|
const configured = process.env.BACKEND_I18N_PREFERENCES_PATH?.trim();
|
|
if (configured) return configured.startsWith('/') ? configured : `/${configured}`;
|
|
return '/api/admin/i18n/preferences';
|
|
}
|
|
|
|
function resolveBackendBaseUrl(): string | null {
|
|
const value = process.env.NEXT_PUBLIC_API_BASE_URL?.trim();
|
|
return value || null;
|
|
}
|
|
|
|
async function proxyPreferencesRequest(request: Request, method: 'GET' | 'POST' | 'PUT' | 'DELETE') {
|
|
console.info('[API][i18n/preferences] start', { method });
|
|
|
|
const access = await requireAdminSession(request);
|
|
if (!access.ok) {
|
|
console.warn('[API][i18n/preferences] denied:admin-session', { method });
|
|
return access.response;
|
|
}
|
|
|
|
const tokenResult = await fetchBackendAccessToken(request);
|
|
if (!tokenResult.ok || !tokenResult.accessToken) {
|
|
console.warn('[API][i18n/preferences] denied:access-token', {
|
|
method,
|
|
status: tokenResult.status,
|
|
message: tokenResult.message,
|
|
});
|
|
const denied = NextResponse.json(
|
|
{ ok: false, message: tokenResult.message ?? 'Unable to obtain backend access token.' },
|
|
{ status: tokenResult.status === 401 ? 401 : 403 }
|
|
);
|
|
|
|
for (const setCookie of tokenResult.setCookies) {
|
|
denied.headers.append('set-cookie', setCookie);
|
|
}
|
|
|
|
return denied;
|
|
}
|
|
|
|
const apiBase = resolveBackendBaseUrl();
|
|
if (!apiBase) {
|
|
console.error('[API][i18n/preferences] missing NEXT_PUBLIC_API_BASE_URL');
|
|
return NextResponse.json({ ok: false, message: 'Missing NEXT_PUBLIC_API_BASE_URL.' }, { status: 500 });
|
|
}
|
|
|
|
const cookie = request.headers.get('cookie') ?? '';
|
|
const backendPath = resolvePreferencesBackendPath();
|
|
|
|
const headers: Record<string, string> = {
|
|
cookie,
|
|
Authorization: `Bearer ${tokenResult.accessToken}`,
|
|
};
|
|
let body: string | undefined;
|
|
|
|
// Build the backend URL; for DELETE we forward the languageCode query param if present.
|
|
const incomingUrl = new URL(request.url);
|
|
const languageCode = incomingUrl.searchParams.get('languageCode');
|
|
const backendQuery = languageCode ? `?languageCode=${encodeURIComponent(languageCode)}` : '';
|
|
const backendUrl = `${apiBase}${backendPath}${backendQuery}`;
|
|
|
|
if (method !== 'GET' && method !== 'DELETE') {
|
|
const payload = await request.json().catch(() => ({}));
|
|
body = JSON.stringify(payload ?? {});
|
|
headers['Content-Type'] = 'application/json';
|
|
|
|
const categoriesCount = Array.isArray((payload as { categories?: unknown[] })?.categories)
|
|
? (payload as { categories?: unknown[] }).categories!.length
|
|
: 0;
|
|
const globalKeysCount = Array.isArray((payload as { globalKeys?: unknown[] })?.globalKeys)
|
|
? (payload as { globalKeys?: unknown[] }).globalKeys!.length
|
|
: 0;
|
|
|
|
console.info('[API][i18n/preferences] outgoing-payload', {
|
|
method,
|
|
categoriesCount,
|
|
globalKeysCount,
|
|
});
|
|
}
|
|
|
|
if (method === 'DELETE' && languageCode) {
|
|
console.info('[API][i18n/preferences] delete-language', { languageCode });
|
|
}
|
|
|
|
const backendResponse = await fetch(backendUrl, {
|
|
method,
|
|
headers,
|
|
body,
|
|
cache: 'no-store',
|
|
}).catch(() => null);
|
|
|
|
if (!backendResponse) {
|
|
console.error('[API][i18n/preferences] backend unreachable', {
|
|
method,
|
|
backendPath,
|
|
});
|
|
const failed = NextResponse.json({ ok: false, message: 'Preferences backend is unreachable.' }, { status: 502 });
|
|
for (const setCookie of tokenResult.setCookies) {
|
|
failed.headers.append('set-cookie', setCookie);
|
|
}
|
|
return failed;
|
|
}
|
|
|
|
console.info('[API][i18n/preferences] backend-response', {
|
|
method,
|
|
status: backendResponse.status,
|
|
ok: backendResponse.ok,
|
|
});
|
|
|
|
const payload = await backendResponse.json().catch(() => null);
|
|
const out = NextResponse.json(payload ?? { ok: backendResponse.ok }, { status: backendResponse.status });
|
|
|
|
for (const setCookie of tokenResult.setCookies) {
|
|
out.headers.append('set-cookie', setCookie);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
export async function GET(request: Request) {
|
|
return proxyPreferencesRequest(request, 'GET');
|
|
}
|
|
|
|
export async function POST(request: Request) {
|
|
return proxyPreferencesRequest(request, 'POST');
|
|
}
|
|
|
|
export async function PUT(request: Request) {
|
|
return proxyPreferencesRequest(request, 'PUT');
|
|
}
|
|
|
|
export async function DELETE(request: Request) {
|
|
return proxyPreferencesRequest(request, 'DELETE');
|
|
}
|