93 lines
3.9 KiB
TypeScript
93 lines
3.9 KiB
TypeScript
import { NextRequest, NextResponse } from 'next/server'
|
|
import http from 'node:http'
|
|
import https from 'node:https'
|
|
|
|
// Service-type routing: NEXT_PUBLIC_MEDUSA_SERVICE_TYPE (from .env.local) selects the correct
|
|
// internal Medusa container. Falls back to ecommerce.
|
|
const _st = process.env.NEXT_PUBLIC_MEDUSA_SERVICE_TYPE || 'ecommerce'
|
|
const _urlMap: Record<string, string> = {
|
|
ecommerce: process.env.VULA_ECOMMERCE_BACKEND_URL || 'http://vula-ecommerce:9000',
|
|
hotel: process.env.VULA_HOTEL_BACKEND_URL || 'http://vula-hotel:9000',
|
|
restaurant: process.env.VULA_RESTAURANT_BACKEND_URL || 'http://vula-restaurant:9000',
|
|
services: process.env.VULA_SERVICES_BACKEND_URL || 'http://vula-services:9000',
|
|
}
|
|
const MEDUSA_URL = _urlMap[_st] || _urlMap.ecommerce
|
|
|
|
function nodeRequest(
|
|
url: string, method: string, headers: Record<string, string>, body?: string, timeoutMs = 15000
|
|
): Promise<{ status: number; data: unknown }> {
|
|
return new Promise((resolve, reject) => {
|
|
const parsed = new URL(url)
|
|
const lib = parsed.protocol === 'https:' ? https : http
|
|
const req = lib.request({
|
|
hostname: parsed.hostname,
|
|
port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80),
|
|
path: parsed.pathname + parsed.search,
|
|
method,
|
|
headers: { ...headers, ...(body ? { 'content-length': Buffer.byteLength(body).toString() } : {}) },
|
|
}, (res) => {
|
|
let raw = ''
|
|
res.setEncoding('utf8')
|
|
res.on('data', (chunk) => { raw += chunk })
|
|
res.on('end', () => {
|
|
try { resolve({ status: res.statusCode ?? 200, data: JSON.parse(raw) }) }
|
|
catch { resolve({ status: res.statusCode ?? 200, data: {} }) }
|
|
})
|
|
})
|
|
req.setTimeout(timeoutMs, () => { req.destroy(new Error('timeout')) })
|
|
req.on('error', reject)
|
|
if (body) req.write(body)
|
|
req.end()
|
|
})
|
|
}
|
|
|
|
async function proxy(req: NextRequest, segments: string[], method: string, body?: string) {
|
|
const search = req.nextUrl.search
|
|
const url = `${MEDUSA_URL}/${segments.join('/')}${search}`
|
|
const headers: Record<string, string> = { 'content-type': 'application/json', accept: 'application/json' }
|
|
const pubKey = req.headers.get('x-publishable-api-key') || process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || ''
|
|
if (pubKey) headers['x-publishable-api-key'] = pubKey
|
|
const timeoutMs = (method === 'POST' && segments.includes('carts')) ? 25000 : 15000
|
|
try {
|
|
const { status, data } = await nodeRequest(url, method, headers, body, timeoutMs)
|
|
return NextResponse.json(data, { status })
|
|
} catch {
|
|
return NextResponse.json({ error: 'Medusa proxy error' }, { status: 502 })
|
|
}
|
|
}
|
|
|
|
type RouteCtx = { params: { path: string[] } }
|
|
export async function GET(req: NextRequest, { params }: RouteCtx) {
|
|
return proxy(req, params.path, 'GET')
|
|
}
|
|
export async function POST(req: NextRequest, { params }: RouteCtx) {
|
|
const path = params.path
|
|
let bodyText = await req.text()
|
|
// Inject project_id server-side for booking/appointment creation so each
|
|
// deployed site's records are scoped to its own sales channel.
|
|
const projectId = process.env.VULA_PROJECT_ID || process.env.VULA_SALES_CHANNEL_ID
|
|
const isBookingPost = projectId && (
|
|
(path.includes('hotel') && path.includes('bookings')) ||
|
|
path.includes('appointments')
|
|
)
|
|
if (isBookingPost && bodyText) {
|
|
try {
|
|
const parsed = JSON.parse(bodyText)
|
|
if (!parsed.project_id) {
|
|
parsed.project_id = projectId
|
|
bodyText = JSON.stringify(parsed)
|
|
}
|
|
} catch { /* leave body unchanged if not JSON */ }
|
|
}
|
|
return proxy(req, path, 'POST', bodyText)
|
|
}
|
|
export async function PUT(req: NextRequest, { params }: RouteCtx) {
|
|
return proxy(req, params.path, 'PUT', await req.text())
|
|
}
|
|
export async function PATCH(req: NextRequest, { params }: RouteCtx) {
|
|
return proxy(req, params.path, 'PATCH', await req.text())
|
|
}
|
|
export async function DELETE(req: NextRequest, { params }: RouteCtx) {
|
|
return proxy(req, params.path, 'DELETE')
|
|
}
|