import { NextResponse } from "next/server"; // Lightweight backend-for-frontend over free, keyless geo services. // GET /api/geo?pin=203201 → India Post: { ok, pin, city, district, state, localities } // GET /api/geo?q=alpha → India Post locality search: { ok, results } // GET /api/geo?addr=350 5th&country=us → OpenStreetMap/Nominatim house-number autosuggest: // { ok, results: [{ label, line1, line2, city, state, postal }] } // If an upstream service is unreachable we fall back to a small built-in dataset // so the registration flow keeps working offline / in the demo. type Locality = { city: string; district: string; state: string; localities: string[] }; const FALLBACK_PINS: Record = { "203201": { city: "Greater Noida", district: "Gautam Buddh Nagar", state: "Uttar Pradesh", localities: ["Dankaur", "Dhanouri Kalan", "Jaganpur Afjalpur"] }, "201301": { city: "Noida", district: "Gautam Buddh Nagar", state: "Uttar Pradesh", localities: ["Noida Sector 1", "Noida Sector 27", "Hoshiarpur"] }, "110001": { city: "New Delhi", district: "Central Delhi", state: "Delhi", localities: ["Connaught Place", "Janpath", "Sansad Marg"] }, "122001": { city: "Gurugram", district: "Gurugram", state: "Haryana", localities: ["Gurgaon", "Civil Lines", "Gurgaon Sector 14"] }, "226001": { city: "Lucknow", district: "Lucknow", state: "Uttar Pradesh", localities: ["Lucknow GPO", "Hazratganj", "Lalbagh"] }, "302001": { city: "Jaipur", district: "Jaipur", state: "Rajasthan", localities: ["Jaipur GPO", "Kishanpole Bazar", "Mi Road"] }, "456010": { city: "Ujjain", district: "Ujjain", state: "Madhya Pradesh", localities: ["Ujjain GPO", "Freeganj", "Mahakal"] }, }; type IndiaPostOffice = { Name: string; District: string; State: string; Pincode: string; BranchType?: string }; type IndiaPostResp = Array<{ Status: string; PostOffice: IndiaPostOffice[] | null }>; async function fetchUpstream(url: string): Promise { const res = await fetch(url, { headers: { "User-Agent": "YEIDA-Portal/1.0 (address autofill)" }, // The PIN → locality mapping is effectively static; cache for a day. next: { revalidate: 86400 }, }); if (!res.ok) throw new Error(`upstream ${res.status}`); return (await res.json()) as IndiaPostResp; } const clean = (s: string) => s.replace(/\s+/g, " ").trim(); export async function GET(request: Request) { const { searchParams } = new URL(request.url); const pin = (searchParams.get("pin") || "").replace(/\D/g, ""); const q = clean(searchParams.get("q") || ""); const addr = clean(searchParams.get("addr") || ""); const country = (searchParams.get("country") || "").toLowerCase().replace(/[^a-z]/g, ""); // --- PIN lookup → city / district / state + locality options --- if (pin) { if (pin.length !== 6) { return NextResponse.json({ ok: false, error: "PIN must be 6 digits" }, { status: 400 }); } try { const data = await fetchUpstream(`https://api.postalpincode.in/pincode/${pin}`); const offices = data?.[0]?.Status === "Success" ? data[0].PostOffice ?? [] : []; if (offices.length) { const first = offices[0]; const localities = [...new Set(offices.map((o) => clean(o.Name)))]; return NextResponse.json({ ok: true, pin, city: first.District, district: first.District, state: first.State, localities, }); } } catch { /* fall through to the built-in fallback below */ } const fb = FALLBACK_PINS[pin]; if (fb) { return NextResponse.json({ ok: true, pin, ...fb, source: "fallback" }); } return NextResponse.json({ ok: false, error: "PIN not found" }, { status: 404 }); } // --- Address / locality typeahead by name --- if (q) { if (q.length < 3) return NextResponse.json({ ok: true, results: [] }); try { const data = await fetchUpstream(`https://api.postalpincode.in/postoffice/${encodeURIComponent(q)}`); const offices = data?.[0]?.Status === "Success" ? data[0].PostOffice ?? [] : []; const results = offices.slice(0, 8).map((o) => ({ label: `${clean(o.Name)} · ${o.District}, ${o.State} — ${o.Pincode}`, line2: clean(o.Name), city: o.District, state: o.State, pin: o.Pincode, })); return NextResponse.json({ ok: true, results }); } catch { const ql = q.toLowerCase(); const results = Object.entries(FALLBACK_PINS) .filter(([, v]) => v.city.toLowerCase().includes(ql) || v.localities.some((l) => l.toLowerCase().includes(ql))) .slice(0, 8) .map(([p, v]) => ({ label: `${v.city} · ${v.district}, ${v.state} — ${p}`, line2: v.city, city: v.city, state: v.state, pin: p })); return NextResponse.json({ ok: true, results, source: "fallback" }); } } // --- Worldwide house-number / street autosuggest (non-India) via OpenStreetMap --- if (addr) { if (addr.length < 3) return NextResponse.json({ ok: true, results: [] }); try { const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(addr)}` + `${country ? `&countrycodes=${country}` : ""}&format=jsonv2&addressdetails=1&limit=6`; const res = await fetch(url, { headers: { "User-Agent": "YEIDA-Portal/1.0 (address autofill)" }, next: { revalidate: 3600 }, }); if (!res.ok) throw new Error(`upstream ${res.status}`); type NomRow = { display_name: string; name?: string; address?: Record }; const rows = (await res.json()) as NomRow[]; const results = rows.map((r) => { const a = r.address || {}; const line1 = clean([a.house_number, a.road || a.pedestrian || a.name || r.name].filter(Boolean).join(" ")); const city = a.city || a.town || a.village || a.municipality || a.suburb || a.county || ""; return { label: r.display_name, line1, line2: a.neighbourhood || a.suburb || a.quarter || "", city, state: a.state || a.region || "", postal: a.postcode || "", }; }).filter((r) => r.line1); return NextResponse.json({ ok: true, results }); } catch { return NextResponse.json({ ok: true, results: [], source: "fallback" }); } } return NextResponse.json({ ok: false, error: "Provide a ?pin=, ?q= or ?addr= parameter" }, { status: 400 }); }