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 <noreply@anthropic.com>
This commit is contained in:
John Mizerek 2026-02-14 19:33:49 -08:00
parent b360284e56
commit be29352b21
4 changed files with 256 additions and 375 deletions

View file

@ -42,7 +42,7 @@ if (bizId LTE 0) {
apiAbort({ OK=false, ERROR="no_business_selected" }); 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; onlyActive = true;
if (structKeyExists(data, "onlyActive")) { if (structKeyExists(data, "onlyActive")) {
if (isBoolean(data.onlyActive)) { if (isBoolean(data.onlyActive)) {
@ -55,71 +55,35 @@ if (structKeyExists(data, "onlyActive")) {
} }
</cfscript> </cfscript>
<!--- Check if this business uses beacon sharding ---> <!--- Get business shard info --->
<cfquery name="qBiz" datasource="payfrit"> <cfquery name="qBiz" datasource="payfrit">
SELECT BeaconShardID, BeaconMajor SELECT b.ID, b.Name, b.BeaconShardID, b.BeaconMajor, bs.UUID AS ShardUUID
FROM Businesses FROM Businesses b
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#"> LEFT JOIN BeaconShards bs ON bs.ID = b.BeaconShardID
WHERE b.ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
LIMIT 1 LIMIT 1
</cfquery> </cfquery>
<cfset usesSharding = qBiz.recordCount GT 0 AND val(qBiz.BeaconShardID) GT 0> <cfif qBiz.recordCount EQ 0>
<cfset shardingInfo = {}> <cfoutput>#serializeJSON({ OK=false, ERROR="business_not_found" })#</cfoutput>
<cfabort>
</cfif>
<cfif usesSharding> <cfset hasShard = val(qBiz.BeaconShardID) GT 0>
<!--- Get shard UUID for display ---> <cfset shardInfo = {
<cfquery name="qShard" datasource="payfrit"> "ShardID" = hasShard ? qBiz.BeaconShardID : 0,
SELECT UUID FROM BeaconShards WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#qBiz.BeaconShardID#"> "ShardUUID" = hasShard ? qBiz.ShardUUID : "",
</cfquery> "Major" = hasShard ? qBiz.BeaconMajor : 0
<cfset shardingInfo = {
"ShardID" = qBiz.BeaconShardID,
"ShardUUID" = qShard.recordCount GT 0 ? qShard.UUID : "",
"Major" = qBiz.BeaconMajor
}> }>
</cfif>
<!--- Legacy beacons from Beacons table ---> <!--- Get service points with beacon minor assignments --->
<cfquery name="q" datasource="payfrit"> <cfquery name="qSP" datasource="payfrit">
SELECT DISTINCT
b.ID,
b.BusinessID,
b.Name,
b.UUID,
b.IsActive
FROM Beacons b
WHERE (
b.BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
OR b.ID IN (
SELECT lt.BeaconID FROM lt_BeaconsID_BusinessesID lt
WHERE lt.BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
)
)
<cfif onlyActive>
AND b.IsActive = 1
</cfif>
ORDER BY b.Name, b.ID
</cfquery>
<cfset beacons = []>
<cfloop query="q">
<cfset arrayAppend(beacons, {
"BeaconID" = q.ID,
"BusinessID" = q.BusinessID,
"Name" = q.Name,
"UUID" = q.UUID,
"IsActive" = q.IsActive,
"IsSharding" = false
})>
</cfloop>
<!--- If using sharding, also show service points with BeaconMinor as "beacons" --->
<cfif usesSharding>
<cfquery name="qShardBeacons" datasource="payfrit">
SELECT SELECT
sp.ID AS ServicePointID, sp.ID AS ServicePointID,
sp.Name, sp.Name,
sp.BeaconMinor, sp.BeaconMinor,
sp.IsActive sp.IsActive,
sp.TypeID
FROM ServicePoints sp FROM ServicePoints sp
WHERE sp.BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#"> WHERE sp.BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
AND sp.BeaconMinor IS NOT NULL AND sp.BeaconMinor IS NOT NULL
@ -129,28 +93,28 @@ if (structKeyExists(data, "onlyActive")) {
ORDER BY sp.BeaconMinor, sp.Name ORDER BY sp.BeaconMinor, sp.Name
</cfquery> </cfquery>
<cfloop query="qShardBeacons"> <cfset beacons = []>
<cfloop query="qSP">
<cfset arrayAppend(beacons, { <cfset arrayAppend(beacons, {
"BeaconID" = 0, "ServicePointID" = qSP.ServicePointID,
"ServicePointID" = qShardBeacons.ServicePointID,
"BusinessID" = bizId, "BusinessID" = bizId,
"Name" = qShardBeacons.Name, "Name" = qSP.Name,
"UUID" = shardingInfo.ShardUUID, "UUID" = shardInfo.ShardUUID,
"Major" = shardingInfo.Major, "Major" = shardInfo.Major,
"Minor" = qShardBeacons.BeaconMinor, "Minor" = qSP.BeaconMinor,
"IsActive" = qShardBeacons.IsActive, "IsActive" = qSP.IsActive ? true : false,
"IsSharding" = true "TypeID" = qSP.TypeID
})> })>
</cfloop> </cfloop>
</cfif>
<cfscript>try{logPerf(0);}catch(any e){}</cfscript> <cfscript>try{logPerf(0);}catch(any e){}</cfscript>
<cfoutput>#serializeJSON({ <cfoutput>#serializeJSON({
OK = true, OK = true,
ERROR = "", ERROR = "",
BusinessID = bizId, BusinessID = bizId,
BusinessName = qBiz.Name,
COUNT = arrayLen(beacons), COUNT = arrayLen(beacons),
BEACONS = beacons, BEACONS = beacons,
USES_SHARDING=usesSharding, HAS_SHARD = hasShard,
SHARDING_INFO=shardingInfo SHARD_INFO = shardInfo
})#</cfoutput> })#</cfoutput>

View file

@ -4,20 +4,11 @@
<cfcontent type="application/json; charset=utf-8" reset="true"> <cfcontent type="application/json; charset=utf-8" reset="true">
<cfheader name="Cache-Control" value="no-store"> <cfheader name="Cache-Control" value="no-store">
<cfscript> <!--- No auth required - returns all Payfrit shard UUIDs for beacon scanning --->
function apiAbort(obj) {
writeOutput(serializeJSON(obj));
abort;
}
</cfscript>
<!--- No auth required - this is public for beacon scanning before login --->
<cfquery name="q" datasource="payfrit"> <cfquery name="q" datasource="payfrit">
SELECT SELECT ID, UUID
ID, FROM BeaconShards
UUID
FROM Beacons
WHERE IsActive = 1 WHERE IsActive = 1
ORDER BY ID ORDER BY ID
</cfquery> </cfquery>
@ -25,7 +16,7 @@ function apiAbort(obj) {
<cfset items = []> <cfset items = []>
<cfloop query="q"> <cfloop query="q">
<cfset arrayAppend(items, { <cfset arrayAppend(items, {
"BeaconID" = q.ID, "ShardID" = q.ID,
"UUID" = q.UUID "UUID" = q.UUID
})> })>
</cfloop> </cfloop>
@ -33,5 +24,6 @@ function apiAbort(obj) {
<cfoutput>#serializeJSON({ <cfoutput>#serializeJSON({
"OK" = true, "OK" = true,
"ERROR" = "", "ERROR" = "",
"SHARDS" = items,
"ITEMS" = items "ITEMS" = items
})#</cfoutput> })#</cfoutput>

View file

@ -6,11 +6,18 @@
<cfset requestData = deserializeJSON(toString(getHttpRequestData().content))> <cfset requestData = deserializeJSON(toString(getHttpRequestData().content))>
<!--- ============================================================================ <!--- Beacon lookup via sharding: { "Beacons": [{ "UUID": "...", "Major": 49, "Minor": 15 }] }
SHARDING FORMAT: { "Beacons": [{ "UUID": "...", "Major": 49, "Minor": 15 }] } Resolves via BeaconShards -> Businesses (BeaconMajor) -> ServicePoints (BeaconMinor) --->
Resolves via BeaconShards -> Businesses (BeaconMajor) -> ServicePoints (BeaconMinor)
============================================================================ ---> <cfif NOT structKeyExists(requestData, "Beacons") OR NOT isArray(requestData.Beacons) OR arrayLen(requestData.Beacons) EQ 0>
<cfif structKeyExists(requestData, "Beacons") AND isArray(requestData.Beacons) AND arrayLen(requestData.Beacons) GT 0> <cfoutput>#serializeJSON({
"OK" = true,
"ERROR" = "",
"BEACONS" = []
})#</cfoutput>
<cfabort>
</cfif>
<cfset beacons = []> <cfset beacons = []>
<cfloop array="#requestData.Beacons#" index="beaconData"> <cfloop array="#requestData.Beacons#" index="beaconData">
@ -39,10 +46,7 @@
<cfcontinue> <cfcontinue>
</cfif> </cfif>
<!--- Resolve via sharding: Namespace (first 20 chars of UUID) -> Shard -> Business (Major) -> ServicePoint (Minor) ---> <!--- Resolve via sharding: UUID -> Shard -> Business (Major) -> ServicePoint (Minor) --->
<!--- Extract namespace (first 20 hex chars) from beacon UUID for matching --->
<cfset namespace = lCase(left(reReplace(uuid, "-", "", "all"), 20))>
<cfquery name="qShard" datasource="payfrit"> <cfquery name="qShard" datasource="payfrit">
SELECT SELECT
biz.ID AS BusinessID, biz.ID AS BusinessID,
@ -51,13 +55,12 @@
COALESCE(parent.Name, '') AS ParentBusinessName, COALESCE(parent.Name, '') AS ParentBusinessName,
sp.ID AS ServicePointID, sp.ID AS ServicePointID,
sp.Name AS ServicePointName, sp.Name AS ServicePointName,
bs.UUID AS ShardUUID,
(SELECT COUNT(*) FROM Businesses WHERE ParentBusinessID = biz.ID) AS ChildCount (SELECT COUNT(*) FROM Businesses WHERE ParentBusinessID = biz.ID) AS ChildCount
FROM BeaconShards bs FROM BeaconShards bs
JOIN Businesses biz ON biz.BeaconShardID = bs.ID AND biz.BeaconMajor = <cfqueryparam cfsqltype="cf_sql_smallint" value="#major#"> JOIN Businesses biz ON biz.BeaconShardID = bs.ID AND biz.BeaconMajor = <cfqueryparam cfsqltype="cf_sql_smallint" value="#major#">
LEFT JOIN ServicePoints sp ON sp.BusinessID = biz.ID AND sp.BeaconMinor = <cfqueryparam cfsqltype="cf_sql_smallint" value="#minor#"> AND sp.IsActive = 1 LEFT JOIN ServicePoints sp ON sp.BusinessID = biz.ID AND sp.BeaconMinor = <cfqueryparam cfsqltype="cf_sql_smallint" value="#minor#"> AND sp.IsActive = 1
LEFT JOIN Businesses parent ON biz.ParentBusinessID = parent.ID LEFT JOIN Businesses parent ON biz.ParentBusinessID = parent.ID
WHERE LEFT(REPLACE(bs.UUID, '-', ''), 20) = <cfqueryparam cfsqltype="cf_sql_varchar" value="#namespace#"> WHERE bs.UUID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#uuid#">
AND bs.IsActive = 1 AND bs.IsActive = 1
AND biz.IsDemo = 0 AND biz.IsDemo = 0
AND biz.IsPrivate = 0 AND biz.IsPrivate = 0
@ -69,8 +72,6 @@
"UUID" = uCase(reReplace(uuid, "-", "", "all")), "UUID" = uCase(reReplace(uuid, "-", "", "all")),
"Major" = major, "Major" = major,
"Minor" = minor, "Minor" = minor,
"BeaconID" = 0,
"BeaconName" = qShard.ServicePointName,
"BusinessID" = qShard.BusinessID, "BusinessID" = qShard.BusinessID,
"BusinessName" = qShard.BusinessName, "BusinessName" = qShard.BusinessName,
"ServicePointID" = val(qShard.ServicePointID), "ServicePointID" = val(qShard.ServicePointID),
@ -82,85 +83,6 @@
</cfif> </cfif>
</cfloop> </cfloop>
<cfoutput>#serializeJSON({
"OK" = true,
"ERROR" = "",
"BEACONS" = beacons
})#</cfoutput>
<cfabort>
</cfif>
<!--- ============================================================================
LEGACY FORMAT: { "UUIDs": ["uuid1", "uuid2", ...] }
Resolves via old Beacons table
============================================================================ --->
<cfif NOT structKeyExists(requestData, "UUIDs") OR NOT isArray(requestData.UUIDs) OR arrayLen(requestData.UUIDs) EQ 0>
<cfoutput>#serializeJSON({
"OK" = true,
"ERROR" = "",
"BEACONS" = []
})#</cfoutput>
<cfabort>
</cfif>
<!--- Clean and normalize UUIDs (remove dashes, uppercase) --->
<cfset cleanUUIDs = []>
<cfloop array="#requestData.UUIDs#" index="uuid">
<cfset cleanUUID = uCase(reReplace(uuid, "-", "", "all"))>
<cfif len(cleanUUID) EQ 32>
<cfset arrayAppend(cleanUUIDs, cleanUUID)>
</cfif>
</cfloop>
<cfif arrayLen(cleanUUIDs) EQ 0>
<cfoutput>#serializeJSON({
"OK" = true,
"ERROR" = "",
"BEACONS" = []
})#</cfoutput>
<cfabort>
</cfif>
<!--- Query for matching beacons with business info --->
<cfquery name="qBeacons" datasource="payfrit">
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 (<cfqueryparam value="#arrayToList(cleanUUIDs)#" cfsqltype="cf_sql_varchar" list="true">)
AND b.IsActive = 1
AND biz.IsDemo = 0
AND biz.IsPrivate = 0
</cfquery>
<cfset beacons = []>
<cfloop query="qBeacons">
<cfset arrayAppend(beacons, {
"UUID" = qBeacons.UUID,
"BeaconID" = qBeacons.BeaconID,
"BeaconName" = qBeacons.BeaconName,
"BusinessID" = qBeacons.BusinessID,
"BusinessName" = qBeacons.BusinessName,
"ServicePointID" = qBeacons.ServicePointID,
"ServicePointName" = qBeacons.ServicePointName,
"ParentBusinessID" = val(qBeacons.ParentBusinessID),
"ParentBusinessName" = qBeacons.ParentBusinessName,
"HasChildren" = qBeacons.ChildCount GT 0
})>
</cfloop>
<cfoutput>#serializeJSON({ <cfoutput>#serializeJSON({
"OK" = true, "OK" = true,
"ERROR" = "", "ERROR" = "",

View file

@ -34,155 +34,158 @@ if (!structKeyExists(request, "BusinessID") || !isNumeric(request.BusinessID) ||
apiAbort({ OK=false, ERROR="no_business_selected" }); apiAbort({ OK=false, ERROR="no_business_selected" });
} }
// Verify the business exists businessID = request.BusinessID;
qBiz = queryTimed(
"SELECT ID FROM Businesses WHERE ID = ? LIMIT 1", // Get business and check if it has a shard assigned
[ { value=request.BusinessID, cfsqltype="cf_sql_integer" } ], qBiz = queryExecute(
"SELECT ID, Name, BeaconShardID, BeaconMajor FROM Businesses WHERE ID = ? LIMIT 1",
[ { value=businessID, cfsqltype="cf_sql_integer" } ],
{ datasource="payfrit" } { datasource="payfrit" }
); );
if (qBiz.recordCount EQ 0) { 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) { // If business doesn't have a shard, allocate one
apiAbort({ OK=false, ERROR="missing_beacon_name", MESSAGE="Name is required" }); shardID = qBiz.BeaconShardID;
} major = qBiz.BeaconMajor;
beaconId = 0; if (isNull(shardID) || val(shardID) EQ 0) {
if (structKeyExists(data, "BeaconID") && isNumeric(data.BeaconID) && int(data.BeaconID) GT 0) { // Find an unassigned shard
beaconId = int(data.BeaconID); qFreeShard = queryExecute(
} "SELECT bs.ID FROM BeaconShards bs
WHERE bs.IsActive = 1
beaconName = normStr(data.Name); AND bs.ID NOT IN (SELECT BeaconShardID FROM Businesses WHERE BeaconShardID IS NOT NULL)
uuid = structKeyExists(data, "UUID") ? normStr(data.UUID) : ""; ORDER BY bs.ID
LIMIT 1",
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" } ],
{ datasource="payfrit" } { 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
}));
</cfscript> </cfscript>
<cfif beaconId GT 0>
<!--- Update - app is authoritative, reassign to current business --->
<cfquery datasource="payfrit">
UPDATE Beacons
SET
BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">,
Name = <cfqueryparam cfsqltype="cf_sql_varchar" value="#beaconName#">,
UUID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#uuid#" null="#(len(uuid) EQ 0)#">,
IsActive = <cfqueryparam cfsqltype="cf_sql_tinyint" value="#isActive#">
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#beaconId#">
</cfquery>
<!--- Update associated service point if it exists, also reassign to current business --->
<cfquery name="qLink" datasource="payfrit">
SELECT ID FROM ServicePoints
WHERE BeaconID = <cfqueryparam cfsqltype="cf_sql_integer" value="#beaconId#">
LIMIT 1
</cfquery>
<cfif qLink.recordCount GT 0>
<cfquery datasource="payfrit">
UPDATE ServicePoints
SET Name = <cfqueryparam cfsqltype="cf_sql_varchar" value="#beaconName#">,
BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
WHERE BeaconID = <cfqueryparam cfsqltype="cf_sql_integer" value="#beaconId#">
</cfquery>
<cfelse>
<!--- No service point exists yet, create one linked to this beacon --->
<cfquery datasource="payfrit">
INSERT INTO ServicePoints (
BusinessID,
Name,
TypeID,
IsActive,
BeaconID
) VALUES (
<cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#beaconName#">,
1,
1,
<cfqueryparam cfsqltype="cf_sql_integer" value="#beaconId#">
)
</cfquery>
</cfif>
<cfelse>
<!--- Insert beacon --->
<cfquery datasource="payfrit">
INSERT INTO Beacons (
BusinessID,
Name,
UUID,
IsActive
) VALUES (
<cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#beaconName#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#uuid#" null="#(len(uuid) EQ 0)#">,
<cfqueryparam cfsqltype="cf_sql_tinyint" value="#isActive#">
)
</cfquery>
<cfquery name="qId" datasource="payfrit">
SELECT LAST_INSERT_ID() AS ID
</cfquery>
<cfset beaconId = qId.ID>
<!--- Auto-create service point with same name, linked to beacon --->
<cfquery datasource="payfrit">
INSERT INTO ServicePoints (
BusinessID,
Name,
TypeID,
IsActive,
BeaconID
) VALUES (
<cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">,
<cfqueryparam cfsqltype="cf_sql_varchar" value="#beaconName#">,
1,
1,
<cfqueryparam cfsqltype="cf_sql_integer" value="#beaconId#">
)
</cfquery>
</cfif>
<!--- Return saved row --->
<cfquery name="qOut" datasource="payfrit">
SELECT
ID,
BusinessID,
Name,
UUID,
IsActive
FROM Beacons
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#beaconId#">
AND BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
LIMIT 1
</cfquery>
<cfset beacon = {
"BeaconID" = qOut.BeaconID,
"BusinessID" = qOut.BusinessID,
"Name" = qOut.Name,
"UUID" = qOut.UUID,
"IsActive" = qOut.IsActive
}>
<cfoutput>#serializeJSON({ OK=true, ERROR="", BEACON=beacon })#</cfoutput>
<cfcatch type="any"> <cfcatch type="any">
<cfheader statuscode="200" statustext="OK"> <cfheader statuscode="200" statustext="OK">
<cfcontent type="application/json; charset=utf-8" reset="true"> <cfcontent type="application/json; charset=utf-8" reset="true">