Skip to content

Commit 226ecc5

Browse files
committed
feat: Refactor Alabama verification logic to directly parse HTML and integrate blob creation and update
1 parent ee81068 commit 226ecc5

2 files changed

Lines changed: 111 additions & 98 deletions

File tree

app/api/verify/alabama/logic.ts

Lines changed: 53 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,59 @@
1+
import { VetResult } from "@/app/types/vet-result";
12

2-
import { VetResult } from "@/app/types/vet-result";
3+
export async function verify(
4+
{ firstName, lastName, licenseNumber }:
5+
{ firstName: string, lastName: string, licenseNumber: string }
6+
): Promise<VetResult[]> {
37

4-
export async function verify({
5-
firstName,
6-
lastName,
7-
licenseNumber,
8-
}: {
9-
firstName: string;
10-
lastName: string;
11-
licenseNumber: string;
12-
}): Promise<VetResult[]> {
138
const queryParams = new URLSearchParams({
14-
license: licenseNumber || "",
15-
firstName: firstName || "",
16-
lastName: lastName || "",
17-
business: "",
18-
city: "",
19-
zipcode: "null",
9+
FirstName: firstName,
10+
LastName: lastName,
11+
LicenseNumber: licenseNumber
2012
});
2113

22-
23-
24-
const html = await fetchHTML(queryParams);
25-
const results = parseHTML(html);
26-
return results;
27-
28-
// 🔹 Internal fetcher
29-
async function fetchHTML(params: URLSearchParams): Promise<string> {
30-
const baseUrl = process.env.VERCEL_URL
31-
? `https://${process.env.VERCEL_URL}`
32-
: "http://localhost:3000";
33-
const res = await fetch(`${baseUrl}/api/verify/alabama?${params.toString()}`);
34-
if (!res.ok) throw new Error("Failed to fetch");
35-
return await res.text();
36-
}
37-
38-
// 🔹 Internal parser
39-
function parseHTML(html: string): VetResult[] {
40-
const parser = new DOMParser();
41-
const doc = parser.parseFromString(html, "text/html");
42-
const rows = Array.from(doc.querySelectorAll("#myDataTable tbody tr"));
43-
44-
return rows
45-
.filter((row) => {
46-
const name = row.querySelector("td")?.textContent?.trim() || "";
47-
return name.includes(" - DVM");
48-
})
49-
.map((row) => {
50-
const cells = row.querySelectorAll("td");
51-
return {
52-
name: cells[0]?.textContent?.trim() || "",
53-
licenseNumber: cells[1]?.textContent?.trim() || "",
54-
issuedDate: cells[2]?.textContent?.trim() || "",
55-
expirationDate: cells[3]?.textContent?.trim() || "",
56-
detailsUrl: cells[4]?.querySelector("a")?.getAttribute("href") || "",
57-
reportUrl: cells[4]?.querySelectorAll("a")[1]?.getAttribute("href") || "",
58-
status: "active",
59-
expiration: cells[3]?.textContent?.trim() || "",
60-
} as VetResult;
61-
});
14+
try {
15+
const res = await fetch(`https://supreme-guacamole-6wjw97ggrr535xgp-3000.app.github.dev/api/verify/alabama?${queryParams.toString()}`);
16+
if (!res.ok) {
17+
throw new Error(`Network response was not ok: ${res.status}`);
18+
}
19+
20+
const html = await res.text();
21+
22+
// Parse HTML (Alabama-specific for now)
23+
const rowRegex = /<tr[^>]*>(.*?)<\/tr>/gs;
24+
const cellRegex = /<td[^>]*>(.*?)<\/td>/gs;
25+
26+
const results: VetResult[] = [];
27+
const rows = html.match(rowRegex) || [];
28+
29+
for (const row of rows) {
30+
const cells = [...row.matchAll(cellRegex)].map(match => match[1].trim());
31+
32+
// Only process rows with at least 4 cells
33+
if (cells.length >= 4) {
34+
const [name, license, , expiration] = cells;
35+
36+
// Filter: must have name, license, and expiration, and name must end with "DVM"
37+
if (
38+
name &&
39+
license &&
40+
expiration &&
41+
/\bDVM\b$/i.test(name)
42+
) {
43+
results.push({
44+
name: name.replace(/\s*-\s*DVM$/i, ""), // Remove " - DVM" suffix for display
45+
licenseNumber: license,
46+
expiration,
47+
status: "Active", // Assuming status is always "Active" for this example
48+
});
49+
}
50+
}
51+
}
52+
53+
return results;
54+
55+
} catch (error) {
56+
console.error("Error fetching Alabama vet data:", error);
57+
return [];
6258
}
63-
64-
65-
// // 💾 Save to JSON locally
66-
// const outputPath = path.join(process.cwd(), "data", "alabamaVets.json");
67-
// fs.writeFileSync(outputPath, JSON.stringify(results, null, 2));
68-
// console.log(`✅ Saved ${results.length} Alabama vet records to ${outputPath}`);
69-
70-
71-
return results;
72-
}
59+
}

app/api/verify/alabama/route.ts

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { verify } from "./logic";
44
import BlobCheck from "@/data/controls/blobs/blobCheck";
55
import { VetResult } from "@/app/types/vet-result";
66
import BlobSync from "@/data/controls/blobs/blobSync";
7+
import BlobCreate from "@/data/controls/blobs/blobCreate";
8+
import BlobUpdate from "@/data/controls/blobs/blobUpdate";
79

810
export async function GET(request: NextRequest) {
911
const { searchParams } = new URL(request.url);
@@ -12,45 +14,69 @@ export async function GET(request: NextRequest) {
1214
const licenseNumber = searchParams.get("license") || "";
1315
const key = "alabama";
1416
const blobKey = `${key}Vets.json`;
15-
const token = process.env.BLOB_READ_WRITE_TOKEN;
1617

17-
try {
18-
// 🔍 Check for existing blob
18+
let updateBlob: any = null;
1919

20+
// 🔍 Check for existing blob
21+
const existingBlob = await BlobCheck(key);
2022

21-
const existingBlob = await BlobCheck(blobKey, [], { token });
23+
if (existingBlob) {
24+
try {
25+
const url = `${process.env.BLOB_STORE_URL}/${blobKey}`;
26+
const response = await fetch(url, {
27+
method: "GET",
28+
headers: {
29+
"User-Agent":
30+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
31+
},
2232

23-
if (existingBlob) {
24-
const blobRes = await fetch(`https://${blobKey}`);
25-
const allResults: VetResult[] = await blobRes.json();
26-
const filtered = allResults.filter((vet) =>
27-
vet.name?.toLowerCase().includes(lastName.toLowerCase()) &&
28-
vet.licenseNumber?.toLowerCase().includes(licenseNumber.toLowerCase())
33+
});
34+
const data = await response.json();
35+
console.log(`✅ Fetched existing blob: ${data}`);
36+
return Response.json(data, {
37+
status: response.status,
38+
});
39+
40+
} catch (error) {
41+
return Response.json(
42+
{
43+
error: error instanceof Error ? error.message : "Failed to fetch Alaska data",
44+
},
45+
{ status: 500 }
2946
);
30-
if (filtered.length) {
31-
return Response.json({
32-
ok: true,
33-
count: filtered.length,
34-
blob: blobKey,
35-
results: filtered,
36-
source: "blob",
37-
});
38-
}
3947
}
40-
// 🌐 Fallback
41-
const results = await verify({ firstName, lastName, licenseNumber });
42-
const blob = await BlobSync(key, results); return Response.json({
43-
ok: true,
44-
count: results.length,
45-
blob,
46-
results,
47-
source: "live",
48+
49+
}
50+
// If blob does not exist, fetch and parse HTML, then create/update blob
51+
52+
if (!existingBlob) {
53+
// 🌐 Fallback: fetch HTML and parse
54+
const url = `https://licensesearch.alabama.gov/ASBVME?${searchParams.toString()}`;
55+
const response = await fetch(url, {
56+
method: "GET",
57+
headers: {
58+
"Content-Type": "text/html",
59+
"User-Agent":
60+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
61+
},
62+
});
63+
64+
const results = await verify({ firstName, lastName, licenseNumber }); // 👈 verify now parses HTML directly
65+
await BlobCreate(key);
66+
await BlobUpdate(key, {
67+
timestamp: new Date().toISOString(),
68+
state: key,
69+
results
4870
});
49-
} catch (error) {
50-
console.error("Fallback HTML fetch:", error);
51-
const url = `https://licensesearch.alabama.gov/ASBVME${searchParams.toString()}`;
52-
const response = await fetch(url, { headers: { "User-Agent": "Mozilla/5.0" } });
71+
// Optionally, sync the blob after update
72+
const blob = await BlobSync(key, results);
73+
5374
const html = await response.text();
54-
return new Response(html, { headers: { "Content-Type": "text/html" } });
75+
return new Response(html, {
76+
headers: { "Content-Type": "text/html" },
77+
status: response.status,
78+
});
5579
}
80+
81+
5682
}

0 commit comments

Comments
 (0)