← back to Dear Bubbe Nextjs
app/api/location/route.ts
195 lines
import { NextRequest, NextResponse } from 'next/server'
// Free IP geolocation API
async function getLocationFromIP(ip: string) {
try {
const response = await fetch(`http://ip-api.com/json/${ip}`)
const data = await response.json()
if (data.status === 'success') {
return {
city: data.city,
state: data.regionName,
stateCode: data.region,
country: data.country,
countryCode: data.countryCode,
zip: data.zip,
lat: data.lat,
lon: data.lon
}
}
} catch (error) {
console.error('[LOCATION] IP lookup failed:', error)
}
return null
}
// Free reverse geocoding using Nominatim (OpenStreetMap)
async function getLocationFromCoords(lat: number, lon: number) {
try {
const response = await fetch(
`https://nominatim.openstreetmap.org/reverse?format=json&lat=${lat}&lon=${lon}&zoom=18&addressdetails=1`,
{
headers: {
'User-Agent': 'BubbeAI/1.0'
}
}
)
const data = await response.json()
if (data && data.address) {
return {
city: data.address.city || data.address.town || data.address.village || data.address.suburb,
state: data.address.state,
stateCode: data.address.state_code,
country: data.address.country,
countryCode: data.address.country_code?.toUpperCase(),
zip: data.address.postcode,
lat: parseFloat(data.lat),
lon: parseFloat(data.lon)
}
}
} catch (error) {
console.error('[LOCATION] Reverse geocoding failed:', error)
}
return null
}
// ZIP code lookup using Zippopotam.us (free, no API key required)
async function getLocationFromZip(zip: string, countryCode: string = 'us') {
try {
// Clean the ZIP code
const cleanZip = zip.replace(/\D/g, '')
const response = await fetch(`http://api.zippopotam.us/${countryCode}/${cleanZip}`)
if (response.ok) {
const data = await response.json()
if (data && data.places && data.places.length > 0) {
const place = data.places[0]
return {
city: place['place name'],
state: place['state'],
stateCode: place['state abbreviation'],
country: data['country'],
countryCode: data['country abbreviation'],
zip: data['post code'],
lat: parseFloat(place['latitude']),
lon: parseFloat(place['longitude'])
}
}
}
} catch (error) {
console.error('[LOCATION] ZIP lookup failed:', error)
}
return null
}
export async function POST(request: NextRequest) {
try {
const body = await request.json()
const { type, lat, lon, zip, countryCode } = body
let location = null
switch (type) {
case 'gps':
// User provided GPS coordinates
if (lat && lon) {
location = await getLocationFromCoords(lat, lon)
}
break
case 'zip':
// User provided ZIP code
if (zip) {
location = await getLocationFromZip(zip, countryCode || 'us')
}
break
case 'ip':
default:
// Fallback to IP-based location
const forwardedFor = request.headers.get('x-forwarded-for')
const realIp = request.headers.get('x-real-ip')
const ip = forwardedFor?.split(',')[0] || realIp || ''
if (ip && ip !== 'unknown') {
location = await getLocationFromIP(ip)
}
break
}
// If we got a location, format it nicely
if (location) {
// Add Bubbe's commentary about the location
let bubbeComment = ''
if (location.city) {
const city = location.city.toLowerCase()
if (city.includes('new york')) {
bubbeComment = "Finally, someone who lives in a real city!"
} else if (city.includes('los angeles') || city.includes('beverly hills')) {
bubbeComment = "LA? All that sunshine and still no spouse?"
} else if (city.includes('miami') || location.state?.toLowerCase().includes('florida')) {
bubbeComment = "Florida? That's where people go to retire, not to live!"
} else if (city.includes('chicago')) {
bubbeComment = "Chicago? It's so cold there, no wonder you're miserable!"
} else if (city.includes('boston')) {
bubbeComment = "Boston? At least they have good schools there."
} else if (city.includes('san francisco')) {
bubbeComment = "San Francisco? With those prices, you better be making good money!"
} else {
bubbeComment = `${location.city}? Never heard of it. Move to New York!`
}
}
return NextResponse.json({
success: true,
location: {
...location,
bubbeComment
}
})
}
return NextResponse.json({
success: false,
error: 'Could not determine location',
location: null
})
} catch (error) {
console.error('[LOCATION] Error:', error)
return NextResponse.json(
{
success: false,
error: 'Failed to get location'
},
{ status: 500 }
)
}
}
// GET endpoint for testing
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams
const zip = searchParams.get('zip')
if (zip) {
const location = await getLocationFromZip(zip)
return NextResponse.json({ location })
}
// Get location from IP
const forwardedFor = request.headers.get('x-forwarded-for')
const realIp = request.headers.get('x-real-ip')
const ip = forwardedFor?.split(',')[0] || realIp || ''
if (ip) {
const location = await getLocationFromIP(ip)
return NextResponse.json({ location })
}
return NextResponse.json({ error: 'No location data available' })
}