Deploy
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
|
||||
// Medusa v2 store API requires region_id to return products with pricing context.
|
||||
// Without it, GET /store/products returns {products: []} even for published products.
|
||||
// Cache in module scope — regions don't change between requests.
|
||||
let cachedRegionId: string | null = null
|
||||
|
||||
async function getRegionId(backendUrl: string, publishableKey: string): Promise<string | null> {
|
||||
if (cachedRegionId) return cachedRegionId
|
||||
// Retry up to 3 times — Medusa may be slow on cold start
|
||||
for (let attempt = 0; attempt < 3; attempt++) {
|
||||
try {
|
||||
const res = await fetch(`${backendUrl}/store/regions?limit=1`, {
|
||||
headers: { 'x-publishable-api-key': publishableKey },
|
||||
cache: 'no-store',
|
||||
signal: AbortSignal.timeout(10000),
|
||||
})
|
||||
if (!res.ok) continue // 5xx during cold start — retry
|
||||
const data = await res.json()
|
||||
const id = data.regions?.[0]?.id ?? null
|
||||
if (id) { cachedRegionId = id; return id }
|
||||
} catch { /* timeout or network error — retry */ }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const { searchParams } = new URL(request.url)
|
||||
// Sanitise limit: reject non-numeric values (e.g. "[object Object]") and clamp 1–100.
|
||||
const rawLimit = searchParams.get('limit') || '12'
|
||||
const parsedLimit = parseInt(rawLimit, 10)
|
||||
const limit = isNaN(parsedLimit) ? 12 : Math.min(Math.max(parsedLimit, 1), 100)
|
||||
const categoryId = searchParams.get('categoryId') || undefined
|
||||
|
||||
// Service-type routing: NEXT_PUBLIC_MEDUSA_SERVICE_TYPE (from .env.local) selects the correct
|
||||
// internal Medusa container. Each container is on a different Docker DNS name + same internal port.
|
||||
const serviceType = process.env.NEXT_PUBLIC_MEDUSA_SERVICE_TYPE || 'ecommerce'
|
||||
const internalUrlMap: 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 backendUrl = internalUrlMap[serviceType] || internalUrlMap.ecommerce
|
||||
const publishableKey = process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || ''
|
||||
|
||||
try {
|
||||
const params = new URLSearchParams({ limit: String(limit) })
|
||||
if (categoryId) params.set('category_id[]', categoryId)
|
||||
const regionId = await getRegionId(backendUrl, publishableKey)
|
||||
if (regionId) {
|
||||
// With region: get calculated_price (tax-aware) + raw prices fallback + categories for filter tabs
|
||||
params.set('region_id', regionId)
|
||||
params.set('fields', '+variants.calculated_price,+variants.prices.amount,+variants.prices.currency_code,+categories')
|
||||
} else {
|
||||
// No region yet: request raw prices so price display still works
|
||||
params.set('fields', '+variants.prices.amount,+variants.prices.currency_code')
|
||||
}
|
||||
const response = await fetch(
|
||||
`${backendUrl}/store/products?${params}`,
|
||||
{
|
||||
headers: { 'x-publishable-api-key': publishableKey },
|
||||
cache: 'no-store',
|
||||
signal: AbortSignal.timeout(10000),
|
||||
}
|
||||
)
|
||||
if (!response.ok) {
|
||||
return NextResponse.json({ products: [] })
|
||||
}
|
||||
const data = await response.json()
|
||||
return NextResponse.json(data)
|
||||
} catch {
|
||||
return NextResponse.json({ products: [] })
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user