- allocate_business_namespace: Start Major from 0 (was 1) - allocate_servicepoint_minor: Start Minor from 0 (was 1) - register_beacon_hardware: Set BeaconMinor if empty instead of rejecting - lookup: Allow Major=0 in validation (was LTE 0, now LT 0) - servicepoints/save: Auto-allocate BeaconMinor on insert, include in response Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
261 lines
8 KiB
Text
261 lines
8 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<cfsetting enablecfoutputonly="true">
|
|
|
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
<cfheader name="Cache-Control" value="no-store">
|
|
|
|
<!---
|
|
RegisterBeaconHardware API
|
|
==========================
|
|
Registers a physical beacon device after provisioning.
|
|
|
|
Request:
|
|
POST /api/beacon-sharding/register_beacon_hardware.cfm
|
|
{
|
|
"HardwareId": "AA:BB:CC:DD:EE:FF",
|
|
"BusinessID": 123,
|
|
"ServicePointID": 456,
|
|
"UUID": "f7826da6-4fa2-4e98-8024-bc5b71e0893e",
|
|
"Major": 42,
|
|
"Minor": 7,
|
|
"TxPower": -59,
|
|
"AdvertisingInterval": 350,
|
|
"FirmwareVersion": "1.2.3"
|
|
}
|
|
|
|
Response:
|
|
{
|
|
"OK": true,
|
|
"BeaconHardwareID": 1,
|
|
"Status": "assigned"
|
|
}
|
|
|
|
Logic:
|
|
1. Validate all required fields
|
|
2. Verify business namespace matches UUID/Major
|
|
3. Verify service point Minor matches
|
|
4. Create or update BeaconHardware record
|
|
--->
|
|
|
|
<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" });
|
|
}
|
|
|
|
bizId = 0;
|
|
if (structKeyExists(data, "BusinessID") && isNumeric(data.BusinessID)) {
|
|
bizId = int(data.BusinessID);
|
|
}
|
|
if (bizId LTE 0) {
|
|
apiAbort({ OK=false, ERROR="missing_business_id", MESSAGE="BusinessID is required" });
|
|
}
|
|
|
|
spId = 0;
|
|
if (structKeyExists(data, "ServicePointID") && isNumeric(data.ServicePointID)) {
|
|
spId = int(data.ServicePointID);
|
|
}
|
|
if (spId LTE 0) {
|
|
apiAbort({ OK=false, ERROR="missing_servicepoint_id", MESSAGE="ServicePointID 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
|
|
txPower = "";
|
|
if (structKeyExists(data, "TxPower") && isNumeric(data.TxPower)) {
|
|
txPower = int(data.TxPower);
|
|
}
|
|
|
|
advInterval = "";
|
|
if (structKeyExists(data, "AdvertisingInterval") && isNumeric(data.AdvertisingInterval)) {
|
|
advInterval = int(data.AdvertisingInterval);
|
|
}
|
|
|
|
firmwareVersion = normStr(structKeyExists(data, "FirmwareVersion") ? data.FirmwareVersion : "");
|
|
</cfscript>
|
|
|
|
<!--- Verify business exists and has matching namespace --->
|
|
<cfquery name="qBiz" datasource="payfrit">
|
|
SELECT b.ID, b.BeaconShardID, b.BeaconMajor, bs.UUID AS ShardUUID
|
|
FROM Businesses b
|
|
LEFT JOIN BeaconShards bs ON b.BeaconShardID = bs.ID
|
|
WHERE b.ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
|
LIMIT 1
|
|
</cfquery>
|
|
|
|
<cfif qBiz.recordCount EQ 0>
|
|
<cfset apiAbort({ OK=false, ERROR="invalid_business", MESSAGE="Business not found" })>
|
|
</cfif>
|
|
|
|
<!--- Verify UUID matches business's shard --->
|
|
<cfif compareNoCase(qBiz.ShardUUID, beaconUUID) NEQ 0>
|
|
<cfset apiAbort({
|
|
OK=false,
|
|
ERROR="uuid_mismatch",
|
|
MESSAGE="UUID does not match business's assigned shard",
|
|
ExpectedUUID=qBiz.ShardUUID,
|
|
ProvidedUUID=beaconUUID
|
|
})>
|
|
</cfif>
|
|
|
|
<!--- Verify Major matches --->
|
|
<cfif qBiz.BeaconMajor NEQ major>
|
|
<cfset apiAbort({
|
|
OK=false,
|
|
ERROR="major_mismatch",
|
|
MESSAGE="Major does not match business's assigned value",
|
|
ExpectedMajor=qBiz.BeaconMajor,
|
|
ProvidedMajor=major
|
|
})>
|
|
</cfif>
|
|
|
|
<!--- Verify service point exists and has matching minor --->
|
|
<cfquery name="qSP" datasource="payfrit">
|
|
SELECT ID, BusinessID, BeaconMinor, Name
|
|
FROM ServicePoints
|
|
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spId#">
|
|
LIMIT 1
|
|
</cfquery>
|
|
|
|
<cfif qSP.recordCount EQ 0>
|
|
<cfset apiAbort({ OK=false, ERROR="invalid_servicepoint", MESSAGE="Service point not found" })>
|
|
</cfif>
|
|
|
|
<cfif qSP.BusinessID NEQ bizId>
|
|
<cfset apiAbort({ OK=false, ERROR="servicepoint_business_mismatch", MESSAGE="Service point does not belong to this business" })>
|
|
</cfif>
|
|
|
|
<!--- If service point doesn't have a minor yet, set it now --->
|
|
<cfif isNull(qSP.BeaconMinor) OR len(trim(qSP.BeaconMinor)) EQ 0>
|
|
<cfquery datasource="payfrit">
|
|
UPDATE ServicePoints
|
|
SET BeaconMinor = <cfqueryparam cfsqltype="cf_sql_smallint" value="#minor#">
|
|
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spId#">
|
|
</cfquery>
|
|
<cfelseif qSP.BeaconMinor NEQ minor>
|
|
<cfset apiAbort({
|
|
OK=false,
|
|
ERROR="minor_mismatch",
|
|
MESSAGE="Minor does not match service point's assigned value",
|
|
ExpectedMinor=qSP.BeaconMinor,
|
|
ProvidedMinor=minor
|
|
})>
|
|
</cfif>
|
|
|
|
<!--- Check if hardware already exists --->
|
|
<cfquery name="qExisting" datasource="payfrit">
|
|
SELECT ID, Status FROM BeaconHardware
|
|
WHERE HardwareId = <cfqueryparam cfsqltype="cf_sql_varchar" value="#hardwareId#">
|
|
LIMIT 1
|
|
</cfquery>
|
|
|
|
<cfif qExisting.recordCount GT 0>
|
|
<!--- Update existing record --->
|
|
<cfquery datasource="payfrit">
|
|
UPDATE BeaconHardware
|
|
SET
|
|
BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">,
|
|
ServicePointID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spId#">,
|
|
ShardUUID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#beaconUUID#">,
|
|
Major = <cfqueryparam cfsqltype="cf_sql_smallint" value="#major#">,
|
|
Minor = <cfqueryparam cfsqltype="cf_sql_smallint" value="#minor#">,
|
|
Status = 'assigned',
|
|
<cfif len(txPower)>TxPower = <cfqueryparam cfsqltype="cf_sql_tinyint" value="#txPower#">,</cfif>
|
|
<cfif len(advInterval)>AdvertisingInterval = <cfqueryparam cfsqltype="cf_sql_smallint" value="#advInterval#">,</cfif>
|
|
<cfif len(firmwareVersion)>FirmwareVersion = <cfqueryparam cfsqltype="cf_sql_varchar" value="#firmwareVersion#">,</cfif>
|
|
UpdatedAt = NOW()
|
|
WHERE HardwareId = <cfqueryparam cfsqltype="cf_sql_varchar" value="#hardwareId#">
|
|
</cfquery>
|
|
<cfset hwId = qExisting.ID>
|
|
<cfelse>
|
|
<!--- Insert new record --->
|
|
<cfquery datasource="payfrit">
|
|
INSERT INTO BeaconHardware (
|
|
HardwareId,
|
|
BusinessID,
|
|
ServicePointID,
|
|
ShardUUID,
|
|
Major,
|
|
Minor,
|
|
Status
|
|
<cfif len(txPower)>,TxPower</cfif>
|
|
<cfif len(advInterval)>,AdvertisingInterval</cfif>
|
|
<cfif len(firmwareVersion)>,FirmwareVersion</cfif>
|
|
) VALUES (
|
|
<cfqueryparam cfsqltype="cf_sql_varchar" value="#hardwareId#">,
|
|
<cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">,
|
|
<cfqueryparam cfsqltype="cf_sql_integer" value="#spId#">,
|
|
<cfqueryparam cfsqltype="cf_sql_varchar" value="#beaconUUID#">,
|
|
<cfqueryparam cfsqltype="cf_sql_smallint" value="#major#">,
|
|
<cfqueryparam cfsqltype="cf_sql_smallint" value="#minor#">,
|
|
'assigned'
|
|
<cfif len(txPower)>,<cfqueryparam cfsqltype="cf_sql_tinyint" value="#txPower#"></cfif>
|
|
<cfif len(advInterval)>,<cfqueryparam cfsqltype="cf_sql_smallint" value="#advInterval#"></cfif>
|
|
<cfif len(firmwareVersion)>,<cfqueryparam cfsqltype="cf_sql_varchar" value="#firmwareVersion#"></cfif>
|
|
)
|
|
</cfquery>
|
|
<cfquery name="qId" datasource="payfrit">
|
|
SELECT LAST_INSERT_ID() AS ID
|
|
</cfquery>
|
|
<cfset hwId = qId.ID>
|
|
</cfif>
|
|
|
|
<cfoutput>#serializeJSON({
|
|
OK = true,
|
|
BeaconHardwareID = hwId,
|
|
HardwareId = hardwareId,
|
|
BusinessID = bizId,
|
|
ServicePointID = spId,
|
|
ServicePointName = qSP.Name,
|
|
UUID = beaconUUID,
|
|
Major = major,
|
|
Minor = minor,
|
|
Status = "assigned"
|
|
})#</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>
|