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/beacon-sharding/allocate_servicepoint_minor.cfm
John Mizerek 3089f84873 Add beacon UUID sharding system and fix task customer info
- 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>
2026-02-06 17:16:08 -08:00

150 lines
4.6 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store">
<!---
AllocateServicePointMinor API
=============================
Allocates or retrieves the beacon Minor value for a service point.
Request:
POST /api/beacon-sharding/allocate_servicepoint_minor.cfm
{ "BusinessID": 123, "ServicePointID": 456 }
Response:
{
"OK": true,
"ServicePointID": 456,
"BusinessID": 123,
"BeaconMinor": 7
}
Logic:
1. Verify service point belongs to the business
2. If already has Minor assigned, return it
3. Otherwise, find next available Minor within the business
4. Minor is stable - does NOT change when hardware is replaced
--->
<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 from: session > body > X-Business-ID header > URL
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 && structKeyExists(url, "BusinessID") && isNumeric(url.BusinessID) && url.BusinessID GT 0) {
bizId = int(url.BusinessID);
}
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 && structKeyExists(url, "ServicePointID") && isNumeric(url.ServicePointID) && url.ServicePointID GT 0) {
spId = int(url.ServicePointID);
}
if (spId LTE 0) {
apiAbort({ OK=false, ERROR="missing_servicepoint_id", MESSAGE="ServicePointID is required" });
}
</cfscript>
<!--- Verify service point exists and belongs to the business --->
<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>
<!--- If already allocated, return existing Minor --->
<cfif NOT isNull(qSP.BeaconMinor)>
<cfoutput>#serializeJSON({
OK = true,
ServicePointID = spId,
BusinessID = bizId,
BeaconMinor = qSP.BeaconMinor,
ServicePointName = qSP.Name,
AlreadyAllocated = true
})#</cfoutput>
<cfabort>
</cfif>
<!--- Find next available Minor within this business --->
<!--- Minor values: 0-65535 (uint16), we start from 1 to avoid 0 --->
<cfquery name="qMaxMinor" datasource="payfrit">
SELECT COALESCE(MAX(BeaconMinor), 0) AS MaxMinor
FROM ServicePoints
WHERE BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
</cfquery>
<cfset nextMinor = qMaxMinor.MaxMinor + 1>
<!--- Sanity check --->
<cfif nextMinor 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="#nextMinor#">
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#spId#">
AND BeaconMinor IS NULL
</cfquery>
<cfoutput>#serializeJSON({
OK = true,
ServicePointID = spId,
BusinessID = bizId,
BeaconMinor = nextMinor,
ServicePointName = qSP.Name,
AlreadyAllocated = false
})#</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>