Add get_beacon_config endpoint — single call for all beacon config
Combines allocate_business_namespace + allocate_servicepoint_minor into one endpoint that returns UUID, Major, Minor, MeasuredPower, AdvInterval, TxPower, ServicePointName, and BusinessName. No more hardcoded defaults on the client. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
95dc4c49fc
commit
f09777eaa5
1 changed files with 206 additions and 0 deletions
206
api/beacon-sharding/get_beacon_config.cfm
Normal file
206
api/beacon-sharding/get_beacon_config.cfm
Normal file
|
|
@ -0,0 +1,206 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
||||||
|
<cfheader name="Cache-Control" value="no-store">
|
||||||
|
|
||||||
|
<!---
|
||||||
|
GetBeaconConfig API
|
||||||
|
===================
|
||||||
|
Single endpoint that returns everything needed to configure a beacon for a service point.
|
||||||
|
Allocates namespace (UUID + Major) and Minor if not already assigned.
|
||||||
|
|
||||||
|
Request:
|
||||||
|
POST /api/beacon-sharding/get_beacon_config.cfm
|
||||||
|
{ "BusinessID": 123, "ServicePointID": 456 }
|
||||||
|
|
||||||
|
Response:
|
||||||
|
{
|
||||||
|
"OK": true,
|
||||||
|
"UUID": "f7826da6-4fa2-4e98-8024-bc5b71e0893e",
|
||||||
|
"Major": 42,
|
||||||
|
"Minor": 7,
|
||||||
|
"MeasuredPower": -100,
|
||||||
|
"AdvInterval": 2,
|
||||||
|
"TxPower": 1,
|
||||||
|
"ServicePointName": "Table 5",
|
||||||
|
"BusinessName": "My Restaurant"
|
||||||
|
}
|
||||||
|
--->
|
||||||
|
|
||||||
|
<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;
|
||||||
|
}
|
||||||
|
|
||||||
|
data = readJsonBody();
|
||||||
|
httpHeaders = getHttpRequestData().headers;
|
||||||
|
|
||||||
|
// Get BusinessID
|
||||||
|
bizId = 0;
|
||||||
|
if (structKeyExists(request, "BusinessID") && isNumeric(request.BusinessID) && request.BusinessID GT 0) {
|
||||||
|
bizId = int(request.BusinessID);
|
||||||
|
}
|
||||||
|
if (bizId LTE 0 && structKeyExists(data, "BusinessID") && isNumeric(data.BusinessID) && data.BusinessID GT 0) {
|
||||||
|
bizId = int(data.BusinessID);
|
||||||
|
}
|
||||||
|
if (bizId LTE 0 && structKeyExists(httpHeaders, "X-Business-ID") && isNumeric(httpHeaders["X-Business-ID"]) && httpHeaders["X-Business-ID"] GT 0) {
|
||||||
|
bizId = int(httpHeaders["X-Business-ID"]);
|
||||||
|
}
|
||||||
|
if (bizId LTE 0) {
|
||||||
|
apiAbort({ OK=false, ERROR="missing_business_id", MESSAGE="BusinessID is required" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get ServicePointID
|
||||||
|
spId = 0;
|
||||||
|
if (structKeyExists(data, "ServicePointID") && isNumeric(data.ServicePointID) && data.ServicePointID GT 0) {
|
||||||
|
spId = int(data.ServicePointID);
|
||||||
|
}
|
||||||
|
if (spId LTE 0) {
|
||||||
|
apiAbort({ OK=false, ERROR="missing_servicepoint_id", MESSAGE="ServicePointID is required" });
|
||||||
|
}
|
||||||
|
</cfscript>
|
||||||
|
|
||||||
|
<!--- Get business with beacon namespace --->
|
||||||
|
<cfquery name="qBiz" datasource="payfrit">
|
||||||
|
SELECT b.ID, b.Name, b.BeaconShardID, b.BeaconMajor
|
||||||
|
FROM Businesses b
|
||||||
|
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>
|
||||||
|
|
||||||
|
<!--- Allocate shard if needed --->
|
||||||
|
<cfif qBiz.BeaconShardID GT 0 AND NOT isNull(qBiz.BeaconMajor)>
|
||||||
|
<!--- Already allocated --->
|
||||||
|
<cfset shardId = qBiz.BeaconShardID>
|
||||||
|
<cfset beaconMajor = qBiz.BeaconMajor>
|
||||||
|
<cfelse>
|
||||||
|
<!--- Find shard with lowest utilization --->
|
||||||
|
<cfquery name="qShard" datasource="payfrit">
|
||||||
|
SELECT ID, UUID, BusinessCount
|
||||||
|
FROM BeaconShards
|
||||||
|
WHERE IsActive = 1
|
||||||
|
AND BusinessCount < MaxBusinesses
|
||||||
|
ORDER BY BusinessCount ASC
|
||||||
|
LIMIT 1
|
||||||
|
FOR UPDATE
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfif qShard.recordCount EQ 0>
|
||||||
|
<cfset apiAbort({ OK=false, ERROR="no_available_shards", MESSAGE="All beacon shards are at capacity" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfset shardId = qShard.ID>
|
||||||
|
|
||||||
|
<!--- Find next available Major --->
|
||||||
|
<cfquery name="qMaxMajor" datasource="payfrit">
|
||||||
|
SELECT COALESCE(MAX(BeaconMajor), -1) AS MaxMajor
|
||||||
|
FROM Businesses
|
||||||
|
WHERE BeaconShardID = <cfqueryparam cfsqltype="cf_sql_integer" value="#shardId#">
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfset beaconMajor = qMaxMajor.MaxMajor + 1>
|
||||||
|
|
||||||
|
<cfif beaconMajor GT 65535>
|
||||||
|
<cfset apiAbort({ OK=false, ERROR="shard_full", MESSAGE="Shard has reached maximum major value" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Assign namespace to business --->
|
||||||
|
<cfquery datasource="payfrit">
|
||||||
|
UPDATE Businesses
|
||||||
|
SET BeaconShardID = <cfqueryparam cfsqltype="cf_sql_integer" value="#shardId#">,
|
||||||
|
BeaconMajor = <cfqueryparam cfsqltype="cf_sql_smallint" value="#beaconMajor#">
|
||||||
|
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
||||||
|
AND (BeaconShardID IS NULL OR BeaconMajor IS NULL)
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<!--- Increment shard's business count --->
|
||||||
|
<cfquery datasource="payfrit">
|
||||||
|
UPDATE BeaconShards
|
||||||
|
SET BusinessCount = BusinessCount + 1
|
||||||
|
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#shardId#">
|
||||||
|
</cfquery>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Get shard UUID --->
|
||||||
|
<cfquery name="qShardUUID" datasource="payfrit">
|
||||||
|
SELECT UUID FROM BeaconShards WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#shardId#">
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<!--- Get service point and allocate Minor if needed --->
|
||||||
|
<cfquery name="qSP" datasource="payfrit">
|
||||||
|
SELECT ID, BusinessID, Name, BeaconMinor
|
||||||
|
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="access_denied", MESSAGE="Service point does not belong to this business" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif NOT isNull(qSP.BeaconMinor)>
|
||||||
|
<cfset beaconMinor = qSP.BeaconMinor>
|
||||||
|
<cfelse>
|
||||||
|
<!--- Find next available Minor within this business --->
|
||||||
|
<cfquery name="qMaxMinor" datasource="payfrit">
|
||||||
|
SELECT COALESCE(MAX(BeaconMinor), -1) AS MaxMinor
|
||||||
|
FROM ServicePoints
|
||||||
|
WHERE BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfset beaconMinor = qMaxMinor.MaxMinor + 1>
|
||||||
|
|
||||||
|
<cfif beaconMinor GT 65535>
|
||||||
|
<cfset apiAbort({ OK=false, ERROR="business_full", MESSAGE="Business has reached maximum service points (65535)" })>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Assign Minor to service point --->
|
||||||
|
<cfquery datasource="payfrit">
|
||||||
|
UPDATE ServicePoints
|
||||||
|
SET BeaconMinor = <cfqueryparam cfsqltype="cf_sql_smallint" value="#beaconMinor#">
|
||||||
|
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spId#">
|
||||||
|
AND BeaconMinor IS NULL
|
||||||
|
</cfquery>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfoutput>#serializeJSON({
|
||||||
|
OK = true,
|
||||||
|
UUID = qShardUUID.UUID,
|
||||||
|
Major = beaconMajor,
|
||||||
|
Minor = beaconMinor,
|
||||||
|
MeasuredPower = -100,
|
||||||
|
AdvInterval = 2,
|
||||||
|
TxPower = 1,
|
||||||
|
ServicePointName = qSP.Name,
|
||||||
|
BusinessName = qBiz.Name
|
||||||
|
})#</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>
|
||||||
Reference in a new issue