Skip to content

Commit 3ee1342

Browse files
authored
feat: Resolve zip to county as well (#120)
1 parent 529611d commit 3ee1342

2 files changed

Lines changed: 112 additions & 7 deletions

File tree

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { NextResponse } from "next/server";
2+
3+
interface FCCCounty {
4+
FIPS: string;
5+
name: string;
6+
}
7+
8+
interface FCCState {
9+
FIPS: string;
10+
code: string;
11+
name: string;
12+
}
13+
14+
export interface FCCResponse {
15+
status: string;
16+
County?: FCCCounty;
17+
State?: FCCState;
18+
}
19+
20+
export async function GET(request: Request) {
21+
const { searchParams } = new URL(request.url);
22+
const latitude = searchParams.get("latitude");
23+
const longitude = searchParams.get("longitude");
24+
25+
if (!latitude || !longitude) {
26+
const missingParams: string[] = [];
27+
if (!latitude) missingParams.push("latitude");
28+
if (!longitude) missingParams.push("longitude");
29+
30+
const errorMessage =
31+
missingParams.length === 1
32+
? `Missing parameter: ${missingParams[0]}`
33+
: `Missing parameters: ${missingParams.join(", ")}`;
34+
35+
return NextResponse.json({ error: errorMessage }, { status: 400 });
36+
}
37+
38+
try {
39+
const params = new URLSearchParams({
40+
format: "json",
41+
latitude,
42+
longitude,
43+
});
44+
45+
const fccUrl = `https://geo.fcc.gov/api/census/block/find?${params.toString()}`;
46+
47+
const fccResponse = await fetch(fccUrl, {
48+
// Add a timeout
49+
signal: AbortSignal.timeout(5000),
50+
});
51+
if (!fccResponse.ok) {
52+
return NextResponse.json(
53+
{ error: "FCC API request failed" },
54+
{ status: fccResponse.status },
55+
);
56+
}
57+
58+
const fccData = (await fccResponse.json()) as FCCResponse;
59+
60+
// Strip " County" suffix from county name if present, for consistency
61+
if (fccData.County?.name) {
62+
fccData.County.name = fccData.County.name.replace(/ County$/i, "");
63+
}
64+
65+
return NextResponse.json(fccData);
66+
} catch (error) {
67+
console.error("Error fetching from FCC API:", error);
68+
return NextResponse.json(
69+
{ error: "Failed to fetch from FCC API" },
70+
{ status: 500 },
71+
);
72+
}
73+
}

frontend/src/util/fetchLocation.ts

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { LRUCache } from "lru-cache";
22

3+
import { FCCResponse } from "@/app/api/fcc-lookup/route";
4+
35
interface ZippopotamPlace {
46
"place name": string; // city name
57
state: string;
@@ -41,12 +43,43 @@ export async function fetchLocationFromZip(zipCode: string): Promise<string> {
4143

4244
const data = (await response.json()) as ZippopotamResponse;
4345
const place = data.places?.[0];
44-
if (place) {
45-
const result = `${place["place name"]}, ${place["state abbreviation"]}`;
46-
// Cache successful result
47-
zipCodeCache.set(zipCode, result);
48-
return result;
46+
if (!place) {
47+
// Cache empty string if no place found
48+
zipCodeCache.set(zipCode, "");
49+
return "";
50+
}
51+
52+
// Query FCC API via our server-side API route to avoid CORS issues
53+
const fccAc = new AbortController();
54+
const timeoutId2 = setTimeout(() => fccAc.abort(), 5_000);
55+
try {
56+
const fccResponse = await fetch(
57+
`/api/fcc-lookup?latitude=${place.latitude}&longitude=${place.longitude}`,
58+
{
59+
signal: fccAc.signal,
60+
},
61+
);
62+
clearTimeout(timeoutId2);
63+
64+
if (fccResponse.ok) {
65+
const fccData = (await fccResponse.json()) as FCCResponse;
66+
67+
// Use county name from FCC and state abbreviation
68+
if (fccData.County && fccData.State) {
69+
const result = `${place["place name"]} (${fccData.County.name} county), ${fccData.State.code}`;
70+
zipCodeCache.set(zipCode, result);
71+
return result;
72+
}
73+
}
74+
} catch (fccError) {
75+
// If FCC API throws any error, fall back to zippopotam's city and state
76+
clearTimeout(timeoutId2);
77+
console.warn("Error fetching from FCC API:", fccError);
4978
}
79+
// Fall back to zippopotam data if FCC fails
80+
const result = `${place["place name"]}, ${place["state abbreviation"]}`;
81+
zipCodeCache.set(zipCode, result);
82+
return result;
5083
} catch (error) {
5184
clearTimeout(timeoutId);
5285
if (error instanceof Error && error.name === "AbortError") {
@@ -55,8 +88,7 @@ export async function fetchLocationFromZip(zipCode: string): Promise<string> {
5588
console.warn("Error fetching city/state from zip code:", error);
5689
}
5790
}
58-
59-
// Cache empty string if no place found or errors (including timeouts)
91+
// Cache empty string if errors
6092
zipCodeCache.set(zipCode, "");
6193
return "";
6294
}

0 commit comments

Comments
 (0)