feat: agregar endpoint /autocomplete en admin Next.js
Proxy a Google Places Autocomplete API:
- Lee la API key de /settings/maps-key del backend
- Acepta params: input, location
- Devuelve { results: [{ formatted_address, place_id }] }
- Headers CORS para permitir acceso desde app.prosapp.co
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
abc19505ae
commit
d3ca99fa1b
@@ -0,0 +1,83 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const API_BASE = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000/api/v1';
|
||||
|
||||
// Cache the Maps API key in memory (refreshes every 5 min)
|
||||
let cachedKey: string | null = null;
|
||||
let cacheTime = 0;
|
||||
|
||||
async function getMapsKey(): Promise<string | null> {
|
||||
const now = Date.now();
|
||||
if (cachedKey && now - cacheTime < 5 * 60 * 1000) return cachedKey;
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/settings/maps-key`, { cache: 'no-store' });
|
||||
if (!res.ok) return null;
|
||||
const data = await res.json();
|
||||
cachedKey = data.api_key || null;
|
||||
cacheTime = now;
|
||||
return cachedKey;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
const { searchParams } = req.nextUrl;
|
||||
const input = searchParams.get('input')?.trim();
|
||||
const location = searchParams.get('location') ?? '';
|
||||
|
||||
if (!input || input.length < 2) {
|
||||
return NextResponse.json({ results: [] });
|
||||
}
|
||||
|
||||
const apiKey = await getMapsKey();
|
||||
if (!apiKey) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Google Maps API key not configured' },
|
||||
{ status: 503 },
|
||||
);
|
||||
}
|
||||
|
||||
// Use Places Autocomplete for typed suggestions
|
||||
const placesUrl = new URL('https://maps.googleapis.com/maps/api/place/autocomplete/json');
|
||||
placesUrl.searchParams.set('input', input);
|
||||
placesUrl.searchParams.set('key', apiKey);
|
||||
placesUrl.searchParams.set('language', 'es');
|
||||
placesUrl.searchParams.set('types', 'address');
|
||||
if (location) placesUrl.searchParams.set('location', location);
|
||||
if (location) placesUrl.searchParams.set('radius', '50000');
|
||||
|
||||
try {
|
||||
const res = await fetch(placesUrl.toString(), { cache: 'no-store' });
|
||||
const data = await res.json();
|
||||
|
||||
if (data.status !== 'OK' && data.status !== 'ZERO_RESULTS') {
|
||||
return NextResponse.json({ results: [] });
|
||||
}
|
||||
|
||||
// Normalize predictions → results with formatted_address for Flutter compatibility
|
||||
const results = (data.predictions ?? []).map((p: any) => ({
|
||||
formatted_address: p.description,
|
||||
place_id: p.place_id,
|
||||
}));
|
||||
|
||||
return NextResponse.json({ results }, {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET',
|
||||
},
|
||||
});
|
||||
} catch {
|
||||
return NextResponse.json({ results: [] });
|
||||
}
|
||||
}
|
||||
|
||||
export async function OPTIONS() {
|
||||
return new NextResponse(null, {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type',
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user