diff --git a/app/api/constituency_lookup/[postcode]/[[...address]]/route.ts b/app/api/constituency_lookup/[postcode]/[[...address]]/route.ts index 1821db0..152fa2e 100644 --- a/app/api/constituency_lookup/[postcode]/[[...address]]/route.ts +++ b/app/api/constituency_lookup/[postcode]/[[...address]]/route.ts @@ -7,6 +7,62 @@ import { validatePostcode, normalizePostcode } from "@/utils/Postcodes"; // Force using 'nodejs' rather than 'edge' - edge won't have the filesystem containing SQLite export const runtime = "nodejs"; +export const revalidate = 3600; + +type DCData = { + boundary_changes?: { + current_constituencies_official_identifier: string; + current_constituencies_name: string; + new_constituencies_official_identifier: string; + new_constituencies_name: string; + CHANGE_TYPE: string; + }; + addresses?: { + address: string; + postcode: string; + slug: string; + url: string; + }[]; +}; + +const dc_base_url = "https://developers.democracyclub.org.uk/api/v1/"; +const dc_params = + "/?" + + new URLSearchParams({ + auth_token: process.env.DC_API_KEY || "", + parl_boundaries: "1", + }).toString(); + +async function fetch_dc_api( + postcode: string, + addressSlug?: string, +): Promise { + const dc_url = + dc_base_url + + (addressSlug ? "address/" + addressSlug : "postcode/" + postcode) + + dc_params; + + const dc_res = await fetch(dc_url, { signal: AbortSignal.timeout(5000) }); + + if (dc_res.ok) { + const dc_json = await dc_res.json(); + if ( + !addressSlug && + dc_json.address_picker && + dc_json.addresses.length > 0 + ) { + return { addresses: dc_json.addresses }; + } else if (dc_json.parl_boundary_changes) { + return { boundary_changes: dc_json.parl_boundary_changes }; + } else { + console.log("DC No Useful Response", await dc_res.json()); + return null; + } + } else { + console.log("DC ERROR", await dc_res.text()); + return null; + } +} // Declare the DB outside the func & lazy-load, so it can be cached across calls let db: Database | null = null; @@ -52,8 +108,9 @@ export async function GET( // Query the database console.time("query-postcode-database"); - const constituencies = await db.all( + const db_constituencies = await db.all( `SELECT + pcon.gss, pcon.slug, pcon.name FROM postcode_lookup @@ -64,7 +121,7 @@ export async function GET( ); console.timeEnd("query-postcode-database"); - if (!constituencies || constituencies.length == 0) { + if (!db_constituencies || db_constituencies.length == 0) { console.log(`Postcode ${normalizedPostcode} not found in DB!`); const response: ConstituencyLookupResponse = { postcode: params.postcode, @@ -75,25 +132,61 @@ export async function GET( return NextResponse.json(response); } - if (constituencies.length == 1) { + if (db_constituencies.length == 1) { console.log(`Single constituency found for postcode ${normalizedPostcode}`); const response: ConstituencyLookupResponse = { postcode: params.postcode, addressSlug: params.address?.[0], - constituencies: constituencies, + constituencies: db_constituencies, }; return NextResponse.json(response); } else { - // TODO: Use DemocracyClub API to lookup postcode and populate the addresses array, so users can select their - // specific address, rather than us expecting to know (or find out) their constituency. console.log( `Multiple constituencies found for postcode ${normalizedPostcode}`, ); + const response: ConstituencyLookupResponse = { postcode: params.postcode, addressSlug: params.address?.[0], - constituencies: constituencies, + constituencies: db_constituencies, }; + + //see if DC api will give us anything useful + //TODO remove this if statement, (here so we can test DC API failure!) + let dc_data = null; + if (normalizedPostcode != "DE30GU") + dc_data = await fetch_dc_api(normalizedPostcode, params.address?.[0]); + + //const dc_data = await fetch_dc_api(normalizedPostcode, params.address?.[0]); + + // See if DC data can return a more useful response. + // Possibly it just matches 1 constituency in which case return that + // Possibly it gives us an address picker which we can return. + if (dc_data?.boundary_changes) { + //Democracy Club data shows this postcode has a single constituency + const gss = + dc_data.boundary_changes.new_constituencies_official_identifier.substring( + 4, + ); + const dc_constituency = db_constituencies.filter((c) => c.gss === gss); + + if (dc_constituency.length === 1) { + response.constituencies = dc_constituency; + } else { + //TODO some way to get vercel to notify us of this error since it shows + //a problem in our or DC data (once DC have fixed their NI & Scottish codes) + console.error(JSON.stringify(dc_data)); + console.error( + "DC returned boundary change data but it didn't match a single db record??", + ); + } + } else if (dc_data?.addresses && dc_data.addresses.length > 0) { + response.addresses = dc_data.addresses.map((addr) => ({ + name: addr.address, + slug: addr.slug, + })); + } + return NextResponse.json(response); } } diff --git a/components/constituency_lookup/ConstituencyLookup.tsx b/components/constituency_lookup/ConstituencyLookup.tsx index 22584ac..77dd239 100644 --- a/components/constituency_lookup/ConstituencyLookup.tsx +++ b/components/constituency_lookup/ConstituencyLookup.tsx @@ -97,9 +97,11 @@ const throttledApi = async ( postcode: string, addressSlug?: string, ): Promise => { - //TODO handle address lookups in the cache if/when we use DemoClub API - if (lookupCache.hasOwnProperty(postcode)) { - const cached = await lookupCache[postcode]; + addressSlug = addressSlug || ""; + const cacheKey = postcode + addressSlug; + + if (!addressSlug && lookupCache.hasOwnProperty(postcode)) { + const cached = await lookupCache[cacheKey]; if (cached) { return cached; } @@ -112,16 +114,15 @@ const throttledApi = async ( lookupCache[reqControl.lastLookup] = null; } - // TODO handle address lookups in the cache. - reqControl.lastLookup = postcode; + reqControl.lastLookup = cacheKey; if (reqControl.time + reqControl.rateLimit < Date.now()) { //Last request was more than rate limit ago. reqControl.time = Date.now(); - lookupCache[postcode] = fetchApi(postcode, addressSlug); + lookupCache[cacheKey] = fetchApi(postcode, addressSlug); } else { //Need to delay the request. - lookupCache[postcode] = new Promise((resolve) => { + lookupCache[cacheKey] = new Promise((resolve) => { let cancelled: boolean = true; reqControl.timerID = setTimeout( () => { @@ -144,7 +145,7 @@ const throttledApi = async ( }); } - return lookupCache[postcode]; + return lookupCache[cacheKey]; }; type FormData = { @@ -206,6 +207,7 @@ const PostcodeLookup = () => { setApiResponse(false); setPostError(null); + console.log("Lookup Constituency", postcode, addressSlug); const responseJson = await throttledApi(postcode, addressSlug); if (postcode != validPostcode.current) { @@ -260,6 +262,10 @@ const PostcodeLookup = () => { if (validatePostcode.test(normalizedPostcode)) { validPostcode.current = normalizedPostcode; await lookupConstituency(normalizedPostcode); + return; + } else { + //Otherwise reset the display of selected postcode + setApiResponse(null); } }; @@ -362,33 +368,75 @@ const PostcodeLookup = () => { {apiResponse && apiResponse.constituencies.length > 1 && ( -
-

- We can't work out exactly which constituency you're in - - please select one of the {apiResponse.constituencies.length}{" "} - options: -

- - setFormState({ - ...formState, - constituencyIndex: parseInt(e.target.value), - }) - } - > - - {apiResponse.constituencies.map((c, idx) => ( - - ))} - -
+ <> + {apiResponse.addresses ? ( +
+

+ We can't work out exactly which constituency you're + in - please select your address: +

+ + lookupConstituency(validPostcode.current, e.target.value) + } + > + + {apiResponse.addresses.map((c) => ( + + ))} + +
+ ) : ( +
+

+ We can't work out exactly which constituency you're + in - please select one of the{" "} + {apiResponse.constituencies.length} options: +

+ { + if (e.target.value.length > 2) { + lookupConstituency(validPostcode.current, e.target.value); + } else { + setFormState({ + ...formState, + constituencyIndex: parseInt(e.target.value), + }); + } + }} + > + + {apiResponse.constituencies.map((c, idx) => ( + + ))} + +
+ )} + )} {subscribed ? (
diff --git a/components/constituency_lookup/types.tsx b/components/constituency_lookup/types.tsx index a55f364..d492cf1 100644 --- a/components/constituency_lookup/types.tsx +++ b/components/constituency_lookup/types.tsx @@ -5,6 +5,7 @@ type Constituency = { name: string; slug: string; + gss?: string; }; type Address = { diff --git a/data/postcodes.db b/data/postcodes.db index da4b996..f2e10cf 100644 Binary files a/data/postcodes.db and b/data/postcodes.db differ