From be29352b21bc117d941cdf0ba4683898eeb899bb Mon Sep 17 00:00:00 2001 From: John Mizerek Date: Sat, 14 Feb 2026 19:33:49 -0800 Subject: [PATCH] Migrate beacon APIs to shard-only system - save.cfm: Auto-allocates shard+Major to business, creates ServicePoint with Minor - list.cfm: Lists ServicePoints with BeaconMinor (removed legacy Beacons table) - list_all.cfm: Returns shard UUIDs instead of legacy beacon UUIDs - lookup.cfm: Removed legacy UUID lookup, shard-only resolution Co-Authored-By: Claude Opus 4.5 --- api/beacons/list.cfm | 128 +++++++----------- api/beacons/list_all.cfm | 18 +-- api/beacons/lookup.cfm | 208 +++++++++-------------------- api/beacons/save.cfm | 277 ++++++++++++++++++++------------------- 4 files changed, 256 insertions(+), 375 deletions(-) diff --git a/api/beacons/list.cfm b/api/beacons/list.cfm index 6df35a2..e84a80e 100644 --- a/api/beacons/list.cfm +++ b/api/beacons/list.cfm @@ -42,7 +42,7 @@ if (bizId LTE 0) { apiAbort({ OK=false, ERROR="no_business_selected" }); } -// Default behavior: only active beacons unless onlyActive is explicitly false/0 +// Default behavior: only active unless onlyActive is explicitly false/0 onlyActive = true; if (structKeyExists(data, "onlyActive")) { if (isBoolean(data.onlyActive)) { @@ -55,102 +55,66 @@ if (structKeyExists(data, "onlyActive")) { } - + - SELECT BeaconShardID, BeaconMajor - FROM Businesses - WHERE ID = + SELECT b.ID, b.Name, b.BeaconShardID, b.BeaconMajor, bs.UUID AS ShardUUID + FROM Businesses b + LEFT JOIN BeaconShards bs ON bs.ID = b.BeaconShardID + WHERE b.ID = LIMIT 1 - - - - - - - SELECT UUID FROM BeaconShards WHERE ID = - - + + #serializeJSON({ OK=false, ERROR="business_not_found" })# + - - - SELECT DISTINCT - b.ID, - b.BusinessID, - b.Name, - b.UUID, - b.IsActive - FROM Beacons b - WHERE ( - b.BusinessID = - OR b.ID IN ( - SELECT lt.BeaconID FROM lt_BeaconsID_BusinessesID lt - WHERE lt.BusinessID = - ) - ) + + + + + + SELECT + sp.ID AS ServicePointID, + sp.Name, + sp.BeaconMinor, + sp.IsActive, + sp.TypeID + FROM ServicePoints sp + WHERE sp.BusinessID = + AND sp.BeaconMinor IS NOT NULL - AND b.IsActive = 1 + AND sp.IsActive = 1 - ORDER BY b.Name, b.ID + ORDER BY sp.BeaconMinor, sp.Name - + - - - - SELECT - sp.ID AS ServicePointID, - sp.Name, - sp.BeaconMinor, - sp.IsActive - FROM ServicePoints sp - WHERE sp.BusinessID = - AND sp.BeaconMinor IS NOT NULL - - AND sp.IsActive = 1 - - ORDER BY sp.BeaconMinor, sp.Name - - - - - - - try{logPerf(0);}catch(any e){} #serializeJSON({ - OK=true, - ERROR="", - BusinessID=bizId, - COUNT=arrayLen(beacons), - BEACONS=beacons, - USES_SHARDING=usesSharding, - SHARDING_INFO=shardingInfo + OK = true, + ERROR = "", + BusinessID = bizId, + BusinessName = qBiz.Name, + COUNT = arrayLen(beacons), + BEACONS = beacons, + HAS_SHARD = hasShard, + SHARD_INFO = shardInfo })# diff --git a/api/beacons/list_all.cfm b/api/beacons/list_all.cfm index 54308ef..5df93b5 100644 --- a/api/beacons/list_all.cfm +++ b/api/beacons/list_all.cfm @@ -4,20 +4,11 @@ - -function apiAbort(obj) { - writeOutput(serializeJSON(obj)); - abort; -} - - - + - SELECT - ID, - UUID - FROM Beacons + SELECT ID, UUID + FROM BeaconShards WHERE IsActive = 1 ORDER BY ID @@ -25,7 +16,7 @@ function apiAbort(obj) { @@ -33,5 +24,6 @@ function apiAbort(obj) { #serializeJSON({ "OK" = true, "ERROR" = "", + "SHARDS" = items, "ITEMS" = items })# diff --git a/api/beacons/lookup.cfm b/api/beacons/lookup.cfm index 8c9d95e..8c6940b 100644 --- a/api/beacons/lookup.cfm +++ b/api/beacons/lookup.cfm @@ -6,95 +6,10 @@ - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 = - LEFT JOIN ServicePoints sp ON sp.BusinessID = biz.ID AND sp.BeaconMinor = AND sp.IsActive = 1 - LEFT JOIN Businesses parent ON biz.ParentBusinessID = parent.ID - WHERE LEFT(REPLACE(bs.UUID, '-', ''), 20) = - AND bs.IsActive = 1 - AND biz.IsDemo = 0 - AND biz.IsPrivate = 0 - LIMIT 1 - - - - - - - - #serializeJSON({ - "OK" = true, - "ERROR" = "", - "BEACONS" = beacons - })# - - - - - + #serializeJSON({ "OK" = true, "ERROR" = "", @@ -103,62 +18,69 @@ - - - - - - - - - - - #serializeJSON({ - "OK" = true, - "ERROR" = "", - "BEACONS" = [] - })# - - - - - - 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 () - AND b.IsActive = 1 - AND biz.IsDemo = 0 - AND biz.IsPrivate = 0 - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SELECT + biz.ID AS BusinessID, + biz.Name AS BusinessName, + biz.ParentBusinessID, + COALESCE(parent.Name, '') AS ParentBusinessName, + sp.ID AS ServicePointID, + sp.Name AS ServicePointName, + (SELECT COUNT(*) FROM Businesses WHERE ParentBusinessID = biz.ID) AS ChildCount + FROM BeaconShards bs + JOIN Businesses biz ON biz.BeaconShardID = bs.ID AND biz.BeaconMajor = + LEFT JOIN ServicePoints sp ON sp.BusinessID = biz.ID AND sp.BeaconMinor = AND sp.IsActive = 1 + LEFT JOIN Businesses parent ON biz.ParentBusinessID = parent.ID + WHERE bs.UUID = + AND bs.IsActive = 1 + AND biz.IsDemo = 0 + AND biz.IsPrivate = 0 + LIMIT 1 + + + + + #serializeJSON({ diff --git a/api/beacons/save.cfm b/api/beacons/save.cfm index cfc0de2..3cfcadc 100644 --- a/api/beacons/save.cfm +++ b/api/beacons/save.cfm @@ -34,155 +34,158 @@ if (!structKeyExists(request, "BusinessID") || !isNumeric(request.BusinessID) || apiAbort({ OK=false, ERROR="no_business_selected" }); } -// Verify the business exists -qBiz = queryTimed( - "SELECT ID FROM Businesses WHERE ID = ? LIMIT 1", - [ { value=request.BusinessID, cfsqltype="cf_sql_integer" } ], +businessID = request.BusinessID; + +// Get business and check if it has a shard assigned +qBiz = queryExecute( + "SELECT ID, Name, BeaconShardID, BeaconMajor FROM Businesses WHERE ID = ? LIMIT 1", + [ { value=businessID, cfsqltype="cf_sql_integer" } ], { datasource="payfrit" } ); if (qBiz.recordCount EQ 0) { - apiAbort({ OK=false, ERROR="invalid_business", MESSAGE="Business ID #request.BusinessID# does not exist. Please log out and log back in." }); + apiAbort({ OK=false, ERROR="invalid_business", MESSAGE="Business not found" }); } -if (!structKeyExists(data, "Name") || len(normStr(data.Name)) EQ 0) { - apiAbort({ OK=false, ERROR="missing_beacon_name", MESSAGE="Name is required" }); -} +// If business doesn't have a shard, allocate one +shardID = qBiz.BeaconShardID; +major = qBiz.BeaconMajor; -beaconId = 0; -if (structKeyExists(data, "BeaconID") && isNumeric(data.BeaconID) && int(data.BeaconID) GT 0) { - beaconId = int(data.BeaconID); -} - -beaconName = normStr(data.Name); -uuid = structKeyExists(data, "UUID") ? normStr(data.UUID) : ""; - -isActive = 1; -if (structKeyExists(data, "IsActive")) { - if (isBoolean(data.IsActive)) isActive = (data.IsActive ? 1 : 0); - else if (isNumeric(data.IsActive)) isActive = int(data.IsActive); - else if (isSimpleValue(data.IsActive)) isActive = (lcase(trim(toString(data.IsActive))) EQ "true" ? 1 : 0); -} - -// App is authoritative: if UUID exists, treat as update (overwrite) -if (beaconId EQ 0 && len(uuid) GT 0) { - qExisting = queryTimed( - "SELECT ID FROM Beacons WHERE UUID = ? LIMIT 1", - [ { value=uuid, cfsqltype="cf_sql_varchar" } ], +if (isNull(shardID) || val(shardID) EQ 0) { + // Find an unassigned shard + qFreeShard = queryExecute( + "SELECT bs.ID FROM BeaconShards bs + WHERE bs.IsActive = 1 + AND bs.ID NOT IN (SELECT BeaconShardID FROM Businesses WHERE BeaconShardID IS NOT NULL) + ORDER BY bs.ID + LIMIT 1", + [], { datasource="payfrit" } ); - if (qExisting.recordCount GT 0) { - beaconId = qExisting.ID; + + if (qFreeShard.recordCount EQ 0) { + apiAbort({ OK=false, ERROR="no_shards_available", MESSAGE="No beacon shards available" }); } + + shardID = qFreeShard.ID; + + // Find next available major for this shard (in case multiple businesses share a shard in future) + qMaxMajor = queryExecute( + "SELECT COALESCE(MAX(BeaconMajor), 0) AS MaxMajor FROM Businesses WHERE BeaconShardID = ?", + [ { value=shardID, cfsqltype="cf_sql_integer" } ], + { datasource="payfrit" } + ); + major = val(qMaxMajor.MaxMajor) + 1; + + // Assign shard to business + queryExecute( + "UPDATE Businesses SET BeaconShardID = ?, BeaconMajor = ? WHERE ID = ?", + [ + { value=shardID, cfsqltype="cf_sql_integer" }, + { value=major, cfsqltype="cf_sql_smallint" }, + { value=businessID, cfsqltype="cf_sql_integer" } + ], + { datasource="payfrit" } + ); } + +// Get the shard UUID +qShard = queryExecute( + "SELECT UUID FROM BeaconShards WHERE ID = ?", + [ { value=shardID, cfsqltype="cf_sql_integer" } ], + { datasource="payfrit" } +); +shardUUID = qShard.UUID; + +// Service point handling +spName = structKeyExists(data, "Name") ? normStr(data.Name) : ""; +if (len(spName) EQ 0) { + apiAbort({ OK=false, ERROR="missing_name", MESSAGE="Service point name is required" }); +} + +servicePointID = 0; +if (structKeyExists(data, "ServicePointID") && isNumeric(data.ServicePointID) && int(data.ServicePointID) GT 0) { + servicePointID = int(data.ServicePointID); +} + +// Check if we're updating an existing service point or creating new +if (servicePointID GT 0) { + // Update existing service point + qSP = queryExecute( + "SELECT ID, BeaconMinor FROM ServicePoints WHERE ID = ? AND BusinessID = ? LIMIT 1", + [ + { value=servicePointID, cfsqltype="cf_sql_integer" }, + { value=businessID, cfsqltype="cf_sql_integer" } + ], + { datasource="payfrit" } + ); + + if (qSP.recordCount EQ 0) { + apiAbort({ OK=false, ERROR="invalid_service_point", MESSAGE="Service point not found" }); + } + + minor = qSP.BeaconMinor; + + // If no minor assigned yet, get next available + if (isNull(minor)) { + qMaxMinor = queryExecute( + "SELECT COALESCE(MAX(BeaconMinor), 0) AS MaxMinor FROM ServicePoints WHERE BusinessID = ?", + [ { value=businessID, cfsqltype="cf_sql_integer" } ], + { datasource="payfrit" } + ); + minor = val(qMaxMinor.MaxMinor) + 1; + } + + // Update service point + queryExecute( + "UPDATE ServicePoints SET Name = ?, BeaconMinor = ?, IsActive = 1 WHERE ID = ?", + [ + { value=spName, cfsqltype="cf_sql_varchar" }, + { value=minor, cfsqltype="cf_sql_smallint" }, + { value=servicePointID, cfsqltype="cf_sql_integer" } + ], + { datasource="payfrit" } + ); + +} else { + // Create new service point with next available minor + qMaxMinor = queryExecute( + "SELECT COALESCE(MAX(BeaconMinor), 0) AS MaxMinor FROM ServicePoints WHERE BusinessID = ?", + [ { value=businessID, cfsqltype="cf_sql_integer" } ], + { datasource="payfrit" } + ); + minor = val(qMaxMinor.MaxMinor) + 1; + + queryExecute( + "INSERT INTO ServicePoints (BusinessID, Name, TypeID, IsActive, BeaconMinor, SortOrder) + VALUES (?, ?, 1, 1, ?, ?)", + [ + { value=businessID, cfsqltype="cf_sql_integer" }, + { value=spName, cfsqltype="cf_sql_varchar" }, + { value=minor, cfsqltype="cf_sql_smallint" }, + { value=minor, cfsqltype="cf_sql_integer" } + ], + { datasource="payfrit" } + ); + + qNewSP = queryExecute("SELECT LAST_INSERT_ID() AS ID", [], { datasource="payfrit" }); + servicePointID = qNewSP.ID; +} + +// Return the beacon configuration for programming +writeOutput(serializeJSON({ + OK = true, + ERROR = "", + ServicePointID = servicePointID, + ServicePointName = spName, + BusinessID = businessID, + BusinessName = qBiz.Name, + ShardID = shardID, + UUID = shardUUID, + Major = major, + Minor = minor +})); - - - - UPDATE Beacons - SET - BusinessID = , - Name = , - UUID = , - IsActive = - WHERE ID = - - - - - SELECT ID FROM ServicePoints - WHERE BeaconID = - LIMIT 1 - - - - UPDATE ServicePoints - SET Name = , - BusinessID = - WHERE BeaconID = - - - - - INSERT INTO ServicePoints ( - BusinessID, - Name, - TypeID, - IsActive, - BeaconID - ) VALUES ( - , - , - 1, - 1, - - ) - - - - - - - INSERT INTO Beacons ( - BusinessID, - Name, - UUID, - IsActive - ) VALUES ( - , - , - , - - ) - - - - SELECT LAST_INSERT_ID() AS ID - - - - - - INSERT INTO ServicePoints ( - BusinessID, - Name, - TypeID, - IsActive, - BeaconID - ) VALUES ( - , - , - 1, - 1, - - ) - - - - - - SELECT - ID, - BusinessID, - Name, - UUID, - IsActive - FROM Beacons - WHERE ID = - AND BusinessID = - LIMIT 1 - - - - -#serializeJSON({ OK=true, ERROR="", BEACON=beacon })# -