This repository has been archived on 2026-03-21. You can view files and clone it, but cannot push or open issues or pull requests.
payfrit-biz/api/businesses/list.cfm
John Mizerek eda8010927 Fix restaurant distance sorting to use numeric comparison
CFML compare() does string comparison, causing distances like 1.9 to
sort after 12.3. Switched to numeric < > operators.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 11:57:45 -08:00

123 lines
3.6 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">
<cfscript>
// Read JSON body for user location
function readJsonBody() {
var raw = getHttpRequestData().content;
if (isNull(raw) OR len(trim(raw)) EQ 0) return {};
try {
var data = deserializeJSON(raw);
if (isStruct(data)) return data;
return {};
} catch (any e) {
return {};
}
}
// Haversine formula to calculate distance in miles
function haversineDistance(lat1, lng1, lat2, lng2) {
var R = 3959; // Earth radius in miles
var dLat = (lat2 - lat1) * 3.14159265359 / 180;
var dLng = (lng2 - lng1) * 3.14159265359 / 180;
var a = sin(dLat/2) * sin(dLat/2) +
cos(lat1 * 3.14159265359 / 180) * cos(lat2 * 3.14159265359 / 180) *
sin(dLng/2) * sin(dLng/2);
// Clamp a to avoid NaN from sqrt of negative or division by zero
if (a < 0) a = 0;
if (a > 1) a = 1;
var c = 2 * asin(sqr(a));
return R * c;
}
try {
data = readJsonBody();
userLat = structKeyExists(data, "lat") ? val(data.lat) : 0;
userLng = structKeyExists(data, "lng") ? val(data.lng) : 0;
hasUserLocation = (userLat != 0 AND userLng != 0);
// Get businesses with their address coordinates (exclude demo and private)
q = queryTimed(
"
SELECT
b.ID,
b.Name,
b.ParentBusinessID,
(SELECT COUNT(*) FROM Businesses c WHERE c.ParentBusinessID = b.ID) AS ChildCount,
a.Latitude AS AddressLat,
a.Longitude AS AddressLng,
a.City AS AddressCity,
a.Line1 AS AddressLine1
FROM Businesses b
LEFT JOIN Addresses a ON b.AddressID = a.ID
WHERE (b.IsDemo = 0 OR b.IsDemo IS NULL)
AND (b.IsPrivate = 0 OR b.IsPrivate IS NULL)
AND (b.ParentBusinessID IS NULL OR b.ParentBusinessID = 0)
ORDER BY b.Name
",
[],
{ datasource = "payfrit" }
);
// Convert query -> array of structs with distance
rows = [];
for (i = 1; i <= q.recordCount; i++) {
row = {
"BusinessID": q.ID[i],
"Name": q.Name[i],
"HasChildren": q.ChildCount[i] > 0,
"City": isNull(q.AddressCity[i]) ? "" : q.AddressCity[i],
"Line1": isNull(q.AddressLine1[i]) ? "" : q.AddressLine1[i],
"Latitude": isNull(q.AddressLat[i]) ? 0 : val(q.AddressLat[i]),
"Longitude": isNull(q.AddressLng[i]) ? 0 : val(q.AddressLng[i])
};
// Calculate distance if we have both user location and business location
bizLat = isNull(q.AddressLat[i]) ? 0 : val(q.AddressLat[i]);
bizLng = isNull(q.AddressLng[i]) ? 0 : val(q.AddressLng[i]);
if (hasUserLocation AND bizLat != 0 AND bizLng != 0) {
row["DistanceMiles"] = haversineDistance(userLat, userLng, bizLat, bizLng);
} else {
row["DistanceMiles"] = 99999; // No location = sort to end
}
arrayAppend(rows, row);
}
// Sort by distance if user location provided
if (hasUserLocation) {
arraySort(rows, function(a, b) {
if (a.DistanceMiles < b.DistanceMiles) return -1;
if (a.DistanceMiles > b.DistanceMiles) return 1;
return 0;
});
}
// Limit to 20 nearest restaurants
if (arrayLen(rows) > 20) {
rows = arraySlice(rows, 1, 20);
}
// Provide BOTH keys to satisfy any Flutter casing expectation
try{logPerf(0);}catch(any e){}
writeOutput(serializeJSON({
"OK": true,
"ERROR": "",
"VERSION": "businesses_list_v5",
"BUSINESSES": rows,
"Businesses": rows
}));
abort;
} catch (any e) {
apiAbort({
"OK": false,
"ERROR": "server_error",
"DETAIL": e.message
});
}
</cfscript>