import { NextRequest, NextResponse } from 'next/server'; import Settings from 'settings'; const VIRTUAL_TRY_ON_API_URL = process.env.NEXT_PUBLIC_VIRTUAL_TRY_ON_API_URL; let limitedCategoriesCache: { data: any; timestamp: number; } | null = null; const CACHE_TTL = 3600000; function getClientIP(request: NextRequest): string | null { const forwardedFor = request.headers.get('x-forwarded-for'); if (forwardedFor) { const ip = forwardedFor.split(',')[0].trim(); if (ip === '::1' || ip === '::ffff:127.0.0.1') { return '127.0.0.1'; } return ip; } const realIP = request.headers.get('x-real-ip'); if (realIP) { if (realIP === '::1' || realIP === '::ffff:127.0.0.1') { return '127.0.0.1'; } return realIP; } if (process.env.NODE_ENV === 'development') { return '127.0.0.1'; } return null; } function getLocaleFromRequest(request: NextRequest): string | null { const cookieLocale = request.cookies.get('pz-locale')?.value; if (cookieLocale) { const currentLocale = Settings.localization.locales.find( (l) => l.value === cookieLocale ); return currentLocale?.apiValue || null; } return null; } export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); const endpoint = searchParams.get('endpoint'); if (endpoint === 'limited-categories') { const now = Date.now(); if ( limitedCategoriesCache && now - limitedCategoriesCache.timestamp < CACHE_TTL ) { return NextResponse.json(limitedCategoriesCache.data, { status: 200, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization', 'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200', 'X-Virtual-Try-On-Cache-Status': 'HIT' } }); } const externalUrl = `${VIRTUAL_TRY_ON_API_URL}/api/v1/limited-categories`; const clientIP = getClientIP(request); const locale = getLocaleFromRequest(request); const headersToSend = { Accept: 'application/json', ...(locale && { 'Accept-Language': locale }), ...(request.headers.get('authorization') && { Authorization: request.headers.get('authorization')! }), ...(clientIP && { 'X-Forwarded-For': clientIP }) }; const response = await fetch(externalUrl, { method: 'GET', headers: headersToSend }); let responseData: any; const responseText = await response.text(); try { responseData = responseText ? JSON.parse(responseText) : {}; } catch (parseError) { responseData = { category_ids: [] }; } if (!response.ok) { responseData = { category_ids: [] }; } limitedCategoriesCache = { data: responseData, timestamp: Date.now() }; return NextResponse.json(responseData, { status: response.ok ? response.status : 200, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization', 'Cache-Control': 'public, s-maxage=3600, stale-while-revalidate=7200', 'X-Virtual-Try-On-Cache-Status': 'MISS' } }); } else if (endpoint === 'job-status') { const referenceUrl = searchParams.get('reference_url'); if (!referenceUrl) { return NextResponse.json( { error: 'reference_url is required' }, { status: 400 } ); } const externalUrl = `${VIRTUAL_TRY_ON_API_URL}/api/async/v1/job-status?reference_url=${encodeURIComponent( referenceUrl )}`; const clientIP = getClientIP(request); const locale = getLocaleFromRequest(request); const headersToSend = { Accept: 'application/json', ...(locale && { 'Accept-Language': locale }), ...(request.headers.get('authorization') && { Authorization: request.headers.get('authorization')! }), ...(clientIP && { 'X-Forwarded-For': clientIP }) }; const response = await fetch(externalUrl, { method: 'GET', headers: headersToSend }); let responseData: any; const responseText = await response.text(); try { responseData = responseText ? JSON.parse(responseText) : {}; } catch (parseError) { return NextResponse.json( { error: 'Invalid JSON response from job status API' }, { status: 500 } ); } return NextResponse.json(responseData, { status: response.status, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization' } }); } return NextResponse.json( { error: 'Invalid endpoint for GET method' }, { status: 400 } ); } catch (error) { return NextResponse.json({ category_ids: [] }, { status: 200 }); } } export async function POST(request: NextRequest) { try { const { searchParams } = new URL(request.url); const endpoint = searchParams.get('endpoint'); const body = await request.json(); let externalUrl: string; let httpMethod = 'POST'; if (endpoint === 'feedback') { if (!body.url || typeof body.url !== 'string' || !body.url.trim()) { return NextResponse.json( { status: 'error', message: 'URL is required for feedback' }, { status: 400 } ); } if (typeof body.feedback !== 'boolean') { return NextResponse.json( { status: 'error', message: 'Feedback must be a boolean value' }, { status: 400 } ); } externalUrl = `${VIRTUAL_TRY_ON_API_URL}/api/v1/feedback`; httpMethod = 'PUT'; } else if (endpoint === 'async-multiple-try-on') { externalUrl = `${VIRTUAL_TRY_ON_API_URL}/api/async/v1/multiple-virtual-try-on`; } else { return NextResponse.json( { error: 'Invalid endpoint specified' }, { status: 400 } ); } const clientIP = getClientIP(request); const locale = getLocaleFromRequest(request); const headersToSend = { 'Content-Type': 'application/json', ...(locale && { 'Accept-Language': locale }), ...(httpMethod === 'POST' && { Accept: 'application/json' }), ...(request.headers.get('authorization') && { Authorization: request.headers.get('authorization')! }), ...(clientIP && { 'X-Forwarded-For': clientIP }) }; const fetchOptions: RequestInit = { method: httpMethod, headers: headersToSend }; if (httpMethod !== 'GET') { fetchOptions.body = JSON.stringify(body); } const response = await fetch(externalUrl, fetchOptions); let responseData: Record; const responseText = await response.text(); if (endpoint === 'feedback') { try { responseData = responseText ? JSON.parse(responseText) : {}; } catch (parseError) { responseData = { error: 'Invalid JSON response from feedback API' }; } } else { try { responseData = responseText ? JSON.parse(responseText) : {}; } catch (parseError) { responseData = { error: 'Invalid JSON response' }; } } if (!response.ok && responseData.error) { let userFriendlyMessage = responseData.error; if ( typeof responseData.error === 'string' && (responseData.error.includes('duplicate key value') || responseData.error.includes('image_pk')) ) { userFriendlyMessage = 'This image has already been processed. Please try with a different image.'; } else if (responseData.error.includes('bulk insert images')) { userFriendlyMessage = 'There was an issue processing your image. Please try again with a different image.'; } return NextResponse.json( { ...responseData, message: userFriendlyMessage }, { status: response.status, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization' } } ); } return NextResponse.json(responseData, { status: response.status, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'POST, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization' } }); } catch (error) { return NextResponse.json( { status: 'error', message: 'Internal server error occurred during virtual try-on processing' }, { status: 500 } ); } } export async function PUT(request: NextRequest) { try { const { searchParams } = new URL(request.url); const endpoint = searchParams.get('endpoint'); if (endpoint !== 'feedback') { return NextResponse.json( { error: 'PUT method only supports feedback endpoint' }, { status: 400 } ); } const body = await request.json(); const externalUrl = `${VIRTUAL_TRY_ON_API_URL}/api/v1/feedback`; const clientIP = getClientIP(request); const locale = getLocaleFromRequest(request); const headersToSend = { 'Content-Type': 'application/json', ...(locale && { 'Accept-Language': locale }), ...(request.headers.get('authorization') && { Authorization: request.headers.get('authorization')! }), ...(clientIP && { 'X-Forwarded-For': clientIP }) }; const response = await fetch(externalUrl, { method: 'PUT', headers: headersToSend, body: JSON.stringify(body) }); const responseData = response?.json() || {}; return NextResponse.json(responseData, { status: response.status, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization' } }); } catch (error) { return NextResponse.json( { status: 'error', message: 'Internal server error occurred during feedback submission', error: (error as Error).message }, { status: 500 } ); } } export async function OPTIONS() { return new NextResponse(null, { status: 200, headers: { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Methods': 'GET, POST, PUT, OPTIONS', 'Access-Control-Allow-Headers': 'Content-Type, Accept, Authorization' } }); }