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/beacons/lookup.cfm
John Mizerek 0bdf9d60b7 Fix beacon lookup to match by namespace (first 20 chars) instead of full UUID
This allows beacons to use any Eddystone-UID configuration where:
- Namespace (10 bytes) matches first 20 hex chars of shard UUID
- Instance bytes encode Major/Minor for business/service point lookup

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-14 17:08:33 -08:00

176 lines
6.4 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cftry>
<cfset requestData = deserializeJSON(toString(getHttpRequestData().content))>
<!--- ============================================================================
SHARDING FORMAT: { "Beacons": [{ "UUID": "...", "Major": 49, "Minor": 15 }] }
Resolves via BeaconShards -> Businesses (BeaconMajor) -> ServicePoints (BeaconMinor)
============================================================================ --->
<cfif structKeyExists(requestData, "Beacons") AND isArray(requestData.Beacons) AND arrayLen(requestData.Beacons) GT 0>
<cfset beacons = []>
<cfloop array="#requestData.Beacons#" index="beaconData">
<cfset uuid = "">
<cfset major = 0>
<cfset minor = -1>
<!--- Extract UUID (normalize: remove dashes, add back in standard format) --->
<cfif structKeyExists(beaconData, "UUID")>
<cfset rawUuid = uCase(reReplace(beaconData.UUID, "-", "", "all"))>
<cfif len(rawUuid) EQ 32>
<!--- Convert to standard UUID format with dashes for DB lookup --->
<cfset uuid = lCase(mid(rawUuid,1,8) & "-" & mid(rawUuid,9,4) & "-" & mid(rawUuid,13,4) & "-" & mid(rawUuid,17,4) & "-" & mid(rawUuid,21,12))>
</cfif>
</cfif>
<cfif structKeyExists(beaconData, "Major") AND isNumeric(beaconData.Major)>
<cfset major = int(beaconData.Major)>
</cfif>
<cfif structKeyExists(beaconData, "Minor") AND isNumeric(beaconData.Minor)>
<cfset minor = int(beaconData.Minor)>
</cfif>
<cfif len(uuid) EQ 0 OR major LT 0 OR minor LT 0>
<cfcontinue>
</cfif>
<!--- Resolve via sharding: Namespace (first 20 chars of UUID) -> Shard -> Business (Major) -> ServicePoint (Minor) --->
<!--- Extract namespace (first 20 hex chars) from beacon UUID for matching --->
<cfset namespace = lCase(left(reReplace(uuid, "-", "", "all"), 20))>
<cfquery name="qShard" datasource="payfrit">
SELECT
biz.ID AS BusinessID,
biz.Name AS BusinessName,
biz.ParentBusinessID,
COALESCE(parent.Name, '') AS ParentBusinessName,
sp.ID AS ServicePointID,
sp.Name AS ServicePointName,
bs.UUID AS ShardUUID,
(SELECT COUNT(*) FROM Businesses WHERE ParentBusinessID = biz.ID) AS ChildCount
FROM BeaconShards bs
JOIN Businesses biz ON biz.BeaconShardID = bs.ID AND biz.BeaconMajor = <cfqueryparam cfsqltype="cf_sql_smallint" value="#major#">
LEFT JOIN ServicePoints sp ON sp.BusinessID = biz.ID AND sp.BeaconMinor = <cfqueryparam cfsqltype="cf_sql_smallint" value="#minor#"> AND sp.IsActive = 1
LEFT JOIN Businesses parent ON biz.ParentBusinessID = parent.ID
WHERE LEFT(REPLACE(bs.UUID, '-', ''), 20) = <cfqueryparam cfsqltype="cf_sql_varchar" value="#namespace#">
AND bs.IsActive = 1
AND biz.IsDemo = 0
AND biz.IsPrivate = 0
LIMIT 1
</cfquery>
<cfif qShard.recordCount GT 0>
<cfset arrayAppend(beacons, {
"UUID" = uCase(reReplace(uuid, "-", "", "all")),
"Major" = major,
"Minor" = minor,
"BeaconID" = 0,
"BeaconName" = qShard.ServicePointName,
"BusinessID" = qShard.BusinessID,
"BusinessName" = qShard.BusinessName,
"ServicePointID" = val(qShard.ServicePointID),
"ServicePointName" = qShard.ServicePointName,
"ParentBusinessID" = val(qShard.ParentBusinessID),
"ParentBusinessName" = qShard.ParentBusinessName,
"HasChildren" = qShard.ChildCount GT 0
})>
</cfif>
</cfloop>
<cfoutput>#serializeJSON({
"OK" = true,
"ERROR" = "",
"BEACONS" = beacons
})#</cfoutput>
<cfabort>
</cfif>
<!--- ============================================================================
LEGACY FORMAT: { "UUIDs": ["uuid1", "uuid2", ...] }
Resolves via old Beacons table
============================================================================ --->
<cfif NOT structKeyExists(requestData, "UUIDs") OR NOT isArray(requestData.UUIDs) OR arrayLen(requestData.UUIDs) EQ 0>
<cfoutput>#serializeJSON({
"OK" = true,
"ERROR" = "",
"BEACONS" = []
})#</cfoutput>
<cfabort>
</cfif>
<!--- Clean and normalize UUIDs (remove dashes, uppercase) --->
<cfset cleanUUIDs = []>
<cfloop array="#requestData.UUIDs#" index="uuid">
<cfset cleanUUID = uCase(reReplace(uuid, "-", "", "all"))>
<cfif len(cleanUUID) EQ 32>
<cfset arrayAppend(cleanUUIDs, cleanUUID)>
</cfif>
</cfloop>
<cfif arrayLen(cleanUUIDs) EQ 0>
<cfoutput>#serializeJSON({
"OK" = true,
"ERROR" = "",
"BEACONS" = []
})#</cfoutput>
<cfabort>
</cfif>
<!--- Query for matching beacons with business info --->
<cfquery name="qBeacons" datasource="payfrit">
SELECT
b.ID AS BeaconID,
b.Name AS BeaconName,
b.UUID,
COALESCE(sp.ID, 0) AS ServicePointID,
COALESCE(sp.Name, '') AS ServicePointName,
COALESCE(sp.BusinessID, lt.BusinessID, b.BusinessID) AS BusinessID,
biz.Name AS BusinessName,
biz.ParentBusinessID AS ParentBusinessID,
COALESCE(parent.Name, '') AS ParentBusinessName,
(SELECT COUNT(*) FROM Businesses WHERE ParentBusinessID = biz.ID) AS ChildCount
FROM Beacons b
LEFT JOIN ServicePoints sp ON sp.BeaconID = b.ID
LEFT JOIN lt_BeaconsID_BusinessesID lt ON lt.BeaconID = b.ID
INNER JOIN Businesses biz ON COALESCE(sp.BusinessID, lt.BusinessID, b.BusinessID) = biz.ID
LEFT JOIN Businesses parent ON biz.ParentBusinessID = parent.ID
WHERE b.UUID IN (<cfqueryparam value="#arrayToList(cleanUUIDs)#" cfsqltype="cf_sql_varchar" list="true">)
AND b.IsActive = 1
AND biz.IsDemo = 0
AND biz.IsPrivate = 0
</cfquery>
<cfset beacons = []>
<cfloop query="qBeacons">
<cfset arrayAppend(beacons, {
"UUID" = qBeacons.UUID,
"BeaconID" = qBeacons.BeaconID,
"BeaconName" = qBeacons.BeaconName,
"BusinessID" = qBeacons.BusinessID,
"BusinessName" = qBeacons.BusinessName,
"ServicePointID" = qBeacons.ServicePointID,
"ServicePointName" = qBeacons.ServicePointName,
"ParentBusinessID" = val(qBeacons.ParentBusinessID),
"ParentBusinessName" = qBeacons.ParentBusinessName,
"HasChildren" = qBeacons.ChildCount GT 0
})>
</cfloop>
<cfoutput>#serializeJSON({
"OK" = true,
"ERROR" = "",
"BEACONS" = beacons
})#</cfoutput>
<cfcatch type="any">
<cfoutput>#serializeJSON({
"OK" = false,
"ERROR" = cfcatch.message
})#</cfoutput>
</cfcatch>
</cftry>