- Add beacon-sharding API endpoints for scalable iBeacon addressing (64 shard UUIDs × 65k businesses = ~4.2M capacity) - Fix callServer.cfm to save UserID when creating Call Server tasks - Fix getDetails.cfm to return customer info from Task.UserID when Order.UserID is null (for tasks without orders) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
171 lines
4.5 KiB
Text
171 lines
4.5 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<cfsetting enablecfoutputonly="true">
|
|
|
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
<cfheader name="Cache-Control" value="no-store">
|
|
|
|
<!---
|
|
VerifyBeaconBroadcast API
|
|
=========================
|
|
Records a scan confirmation that a beacon is broadcasting the expected values.
|
|
Called by provisioning app after writing config and seeing the beacon advertise.
|
|
|
|
Request:
|
|
POST /api/beacon-sharding/verify_beacon_broadcast.cfm
|
|
{
|
|
"HardwareId": "AA:BB:CC:DD:EE:FF",
|
|
"UUID": "f7826da6-4fa2-4e98-8024-bc5b71e0893e",
|
|
"Major": 42,
|
|
"Minor": 7,
|
|
"RSSI": -65,
|
|
"SeenAt": "2024-01-15T10:30:00Z"
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"OK": true,
|
|
"BeaconHardwareID": 1,
|
|
"Status": "verified",
|
|
"VerifiedAt": "2024-01-15T10:30:00Z"
|
|
}
|
|
|
|
Logic:
|
|
1. Find beacon hardware by HardwareId
|
|
2. Verify UUID/Major/Minor match expected values
|
|
3. Update status to "verified" and record timestamp/RSSI
|
|
--->
|
|
|
|
<cftry>
|
|
<cfscript>
|
|
function apiAbort(obj) {
|
|
writeOutput(serializeJSON(obj));
|
|
abort;
|
|
}
|
|
|
|
function readJsonBody() {
|
|
raw = toString(getHttpRequestData().content);
|
|
if (isNull(raw) || len(trim(raw)) EQ 0) return {};
|
|
try {
|
|
parsed = deserializeJSON(raw);
|
|
} catch(any e) {
|
|
apiAbort({ OK=false, ERROR="bad_json", MESSAGE="Invalid JSON body" });
|
|
}
|
|
if (!isStruct(parsed)) return {};
|
|
return parsed;
|
|
}
|
|
|
|
function normStr(v) {
|
|
if (isNull(v)) return "";
|
|
return trim(toString(v));
|
|
}
|
|
|
|
data = readJsonBody();
|
|
|
|
// Required fields
|
|
hardwareId = normStr(structKeyExists(data, "HardwareId") ? data.HardwareId : "");
|
|
if (len(hardwareId) EQ 0) {
|
|
apiAbort({ OK=false, ERROR="missing_hardware_id", MESSAGE="HardwareId is required" });
|
|
}
|
|
|
|
beaconUUID = normStr(structKeyExists(data, "UUID") ? data.UUID : "");
|
|
if (len(beaconUUID) EQ 0) {
|
|
apiAbort({ OK=false, ERROR="missing_uuid", MESSAGE="UUID is required" });
|
|
}
|
|
|
|
major = 0;
|
|
if (structKeyExists(data, "Major") && isNumeric(data.Major)) {
|
|
major = int(data.Major);
|
|
}
|
|
|
|
minor = 0;
|
|
if (structKeyExists(data, "Minor") && isNumeric(data.Minor)) {
|
|
minor = int(data.Minor);
|
|
}
|
|
|
|
// Optional fields
|
|
rssi = "";
|
|
if (structKeyExists(data, "RSSI") && isNumeric(data.RSSI)) {
|
|
rssi = int(data.RSSI);
|
|
}
|
|
|
|
seenAt = now();
|
|
if (structKeyExists(data, "SeenAt") && len(data.SeenAt)) {
|
|
try {
|
|
seenAt = parseDateTime(data.SeenAt);
|
|
} catch(any e) {
|
|
// Use current time if parse fails
|
|
}
|
|
}
|
|
</cfscript>
|
|
|
|
<!--- Find beacon hardware --->
|
|
<cfquery name="qHW" datasource="payfrit">
|
|
SELECT ID, BusinessID, ServicePointID, ShardUUID, Major, Minor, Status
|
|
FROM BeaconHardware
|
|
WHERE HardwareId = <cfqueryparam cfsqltype="cf_sql_varchar" value="#hardwareId#">
|
|
LIMIT 1
|
|
</cfquery>
|
|
|
|
<cfif qHW.recordCount EQ 0>
|
|
<cfset apiAbort({ OK=false, ERROR="hardware_not_found", MESSAGE="Beacon hardware not registered" })>
|
|
</cfif>
|
|
|
|
<!--- Verify broadcast matches expected values --->
|
|
<cfif compareNoCase(qHW.ShardUUID, beaconUUID) NEQ 0>
|
|
<cfset apiAbort({
|
|
OK=false,
|
|
ERROR="uuid_mismatch",
|
|
MESSAGE="Beacon is broadcasting wrong UUID",
|
|
ExpectedUUID=qHW.ShardUUID,
|
|
BroadcastUUID=beaconUUID
|
|
})>
|
|
</cfif>
|
|
|
|
<cfif qHW.Major NEQ major>
|
|
<cfset apiAbort({
|
|
OK=false,
|
|
ERROR="major_mismatch",
|
|
MESSAGE="Beacon is broadcasting wrong Major",
|
|
ExpectedMajor=qHW.Major,
|
|
BroadcastMajor=major
|
|
})>
|
|
</cfif>
|
|
|
|
<cfif qHW.Minor NEQ minor>
|
|
<cfset apiAbort({
|
|
OK=false,
|
|
ERROR="minor_mismatch",
|
|
MESSAGE="Beacon is broadcasting wrong Minor",
|
|
ExpectedMinor=qHW.Minor,
|
|
BroadcastMinor=minor
|
|
})>
|
|
</cfif>
|
|
|
|
<!--- Update to verified status --->
|
|
<cfquery datasource="payfrit">
|
|
UPDATE BeaconHardware
|
|
SET
|
|
Status = 'verified',
|
|
VerifiedAt = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#seenAt#">,
|
|
LastSeenAt = <cfqueryparam cfsqltype="cf_sql_timestamp" value="#seenAt#">,
|
|
<cfif len(rssi)>LastRssi = <cfqueryparam cfsqltype="cf_sql_smallint" value="#rssi#">,</cfif>
|
|
UpdatedAt = NOW()
|
|
WHERE HardwareId = <cfqueryparam cfsqltype="cf_sql_varchar" value="#hardwareId#">
|
|
</cfquery>
|
|
|
|
<cfoutput>#serializeJSON({
|
|
OK = true,
|
|
BeaconHardwareID = qHW.ID,
|
|
HardwareId = hardwareId,
|
|
BusinessID = qHW.BusinessID,
|
|
ServicePointID = qHW.ServicePointID,
|
|
Status = "verified",
|
|
VerifiedAt = dateTimeFormat(seenAt, "yyyy-mm-dd'T'HH:nn:ss'Z'")
|
|
})#</cfoutput>
|
|
|
|
<cfcatch type="any">
|
|
<cfheader statuscode="200" statustext="OK">
|
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
<cfoutput>#serializeJSON({ OK=false, ERROR="server_error", MESSAGE=cfcatch.message, DETAIL=cfcatch.detail })#</cfoutput>
|
|
</cfcatch>
|
|
</cftry>
|