admin/services/mapirReverseGeocode.ts
alisaza e1eaf5eff5 feat: initial ghabilee-admin backoffice app
Extract admin dashboard from ghabilee-frontend2 into a dedicated Next.js
app for backoffice.ghabilee.ir (no SEO indexing / Clarity).
2026-09-05 13:12:59 +03:30

67 lines
1.6 KiB
TypeScript

/** Map.ir reverse geocode (client; NEXT_PUBLIC_MAP_API_KEY). https://map.ir/reverse/no */
import { texts } from '@/texts'
const REVERSE_BASE = 'https://map.ir/reverse/no'
const v = texts.validation.map
/** Map.ir response address field only (official API) */
function pickAddressFromBody(data: unknown): string | null {
if (!data || typeof data !== 'object') return null
const address = (data as Record<string, unknown>).address
if (typeof address === 'string' && address.trim()) return address.trim()
return null
}
/** @returns `address` field or empty string; throws Error on failure */
export async function reverseGeocodeMapIr(lat: number, lng: number, apiKey: string): Promise<string> {
if (!Number.isFinite(lat) || !Number.isFinite(lng)) {
throw new Error(v.invalidCoords)
}
const key = apiKey.trim()
if (!key) {
throw new Error(v.mapKeyMissing)
}
const url = `${REVERSE_BASE}?lat=${encodeURIComponent(String(lat))}&lon=${encodeURIComponent(String(lng))}`
let res: Response
try {
res = await fetch(url, {
headers: {
'Content-Type': 'application/json',
'x-api-key': key,
},
})
} catch {
throw new Error(v.mapServiceError)
}
let data: unknown
try {
data = await res.json()
} catch {
throw new Error(v.mapInvalidResponse)
}
if (!res.ok) {
throw new Error(v.mapAddressFailed)
}
const statusField = (data as { status?: unknown })?.status
if (typeof statusField === 'number' && statusField >= 400) {
throw new Error(v.mapAddressFailed)
}
const picked = pickAddressFromBody(data)
return picked ?? ''
}