function apiAbort(payload) { writeOutput(serializeJSON(payload)); abort; } // 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 = queryExecute( " SELECT b.BusinessID, b.BusinessName, a.AddressLat, a.AddressLng, a.AddressCity, a.AddressLine1 FROM Businesses b LEFT JOIN Addresses a ON b.BusinessAddressID = a.AddressID WHERE (b.BusinessIsDemo = 0 OR b.BusinessIsDemo IS NULL) AND (b.BusinessIsPrivate = 0 OR b.BusinessIsPrivate IS NULL) ORDER BY b.BusinessName ", [], { datasource = "payfrit" } ); // Convert query -> array of structs with distance rows = []; for (i = 1; i <= q.recordCount; i++) { row = { "BusinessID": q.BusinessID[i], "BusinessName": q.BusinessName[i], "AddressCity": isNull(q.AddressCity[i]) ? "" : q.AddressCity[i], "AddressLine1": isNull(q.AddressLine1[i]) ? "" : q.AddressLine1[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) { return compare(a.DistanceMiles, b.DistanceMiles); }); } // Limit to 20 nearest restaurants if (arrayLen(rows) > 20) { rows = arraySlice(rows, 1, 20); } // Provide BOTH keys to satisfy any Flutter casing expectation 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 }); }