[object Object]

← back to NationalPaperHangers

Fix /api/near-me: accept county/town/zip filters without coordinates

2e14b8b1508c9f4db6d86c5b993f76554ad4065c · 2026-05-18 16:21:02 -0700 · SteveStudio2

The handler computed countyHit/townHit/zipHit matches but the guard
rejected every request lacking lat/lng with 400 — so those three match
paths were unreachable dead logic. Now accepts a request with coordinates
OR any county/town/zip filter; distance_miles is null for filter-only
matches and they sort last. (Endpoint had zero callers since the /map
rewrite — keeping it as a working JSON API rather than shipping it broken.)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit 2e14b8b1508c9f4db6d86c5b993f76554ad4065c
Author: SteveStudio2 <steve@designerwallcoverings.com>
Date:   Mon May 18 16:21:02 2026 -0700

    Fix /api/near-me: accept county/town/zip filters without coordinates
    
    The handler computed countyHit/townHit/zipHit matches but the guard
    rejected every request lacking lat/lng with 400 — so those three match
    paths were unreachable dead logic. Now accepts a request with coordinates
    OR any county/town/zip filter; distance_miles is null for filter-only
    matches and they sort last. (Endpoint had zero callers since the /map
    rewrite — keeping it as a working JSON API rather than shipping it broken.)
    
    Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---
 routes/public.js | 27 ++++++++++++++++++++-------
 1 file changed, 20 insertions(+), 7 deletions(-)

diff --git a/routes/public.js b/routes/public.js
index b848c7a..79abbd1 100644
--- a/routes/public.js
+++ b/routes/public.js
@@ -861,8 +861,11 @@ router.get('/api/near-me', async (req, res, next) => {
     const county = String(req.query.county || '').replace(/\s+County$/i, '').trim().toLowerCase();
     const town = String(req.query.town || '').trim().toLowerCase();
     const zip = String(req.query.zip || '').trim();
-    if (!Number.isFinite(lat) || !Number.isFinite(lng)) {
-      return res.status(400).json({ error: 'lat/lng required' });
+    const hasCoords = Number.isFinite(lat) && Number.isFinite(lng);
+    // Accept either coordinates (radius search) OR a county/town/zip filter —
+    // the match logic below supports all four. Reject only when none are given.
+    if (!hasCoords && !county && !town && !zip) {
+      return res.status(400).json({ error: 'lat/lng or county/town/zip required' });
     }
     const rows = await db.many(
       `SELECT i.id, i.slug, i.business_name, i.city, i.state,
@@ -875,8 +878,8 @@ router.get('/api/near-me', async (req, res, next) => {
     );
     const matches = [];
     for (const r of rows) {
-      const d = distanceMiles(lat, lng, r.lat, r.lng);
-      const radiusHit = d <= (r.service_radius_miles || 50);
+      const d = hasCoords ? distanceMiles(lat, lng, r.lat, r.lng) : null;
+      const radiusHit = d !== null && d <= (r.service_radius_miles || 50);
       const countyHit = county && (r.service_counties || []).some(c =>
         c.toLowerCase().replace(/\s+county$/i, '').trim() === county);
       const townHit = town && (r.service_towns || []).some(t => t.toLowerCase().trim() === town);
@@ -888,13 +891,23 @@ router.get('/api/near-me', async (req, res, next) => {
           lat: r.lat, lng: r.lng,
           tier: r.tier, verified: r.verified, claim_status: r.claim_status,
           response_time_hours: r.response_time_hours,
-          distance_miles: Math.round(d * 10) / 10
+          distance_miles: d === null ? null : Math.round(d * 10) / 10
         });
       }
     }
-    matches.sort((a, b) => a.distance_miles - b.distance_miles);
+    // Sort by distance when coordinates were supplied; nulls (filter-only
+    // matches) sort last.
+    matches.sort((a, b) => {
+      if (a.distance_miles === null) return b.distance_miles === null ? 0 : 1;
+      if (b.distance_miles === null) return -1;
+      return a.distance_miles - b.distance_miles;
+    });
     res.set('cache-control', 'public, max-age=60');
-    res.json({ count: matches.length, user: { lat, lng }, installers: matches });
+    res.json({
+      count: matches.length,
+      user: hasCoords ? { lat, lng } : { county: county || null, town: town || null, zip: zip || null },
+      installers: matches
+    });
   } catch (err) { next(err); }
 });
 

← 283685a Clear all 6 npm audit vulns — audit fix + bcrypt 5.x→6.0.0  ·  back to NationalPaperHangers  ·  Surface the structured booking brief in the installer notifi 841f9be →