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:
parent
b360284e56
commit
be29352b21
4 changed files with 256 additions and 375 deletions
|
|
@ -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,102 +55,66 @@ 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 usesSharding>
|
|
||||||
<!--- Get shard UUID for display --->
|
|
||||||
<cfquery name="qShard" datasource="payfrit">
|
|
||||||
SELECT UUID FROM BeaconShards WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#qBiz.BeaconShardID#">
|
|
||||||
</cfquery>
|
|
||||||
<cfset shardingInfo = {
|
|
||||||
"ShardID" = qBiz.BeaconShardID,
|
|
||||||
"ShardUUID" = qShard.recordCount GT 0 ? qShard.UUID : "",
|
|
||||||
"Major" = qBiz.BeaconMajor
|
|
||||||
}>
|
|
||||||
</cfif>
|
</cfif>
|
||||||
|
|
||||||
<!--- Legacy beacons from Beacons table --->
|
<cfset hasShard = val(qBiz.BeaconShardID) GT 0>
|
||||||
<cfquery name="q" datasource="payfrit">
|
<cfset shardInfo = {
|
||||||
SELECT DISTINCT
|
"ShardID" = hasShard ? qBiz.BeaconShardID : 0,
|
||||||
b.ID,
|
"ShardUUID" = hasShard ? qBiz.ShardUUID : "",
|
||||||
b.BusinessID,
|
"Major" = hasShard ? qBiz.BeaconMajor : 0
|
||||||
b.Name,
|
}>
|
||||||
b.UUID,
|
|
||||||
b.IsActive
|
<!--- Get service points with beacon minor assignments --->
|
||||||
FROM Beacons b
|
<cfquery name="qSP" datasource="payfrit">
|
||||||
WHERE (
|
SELECT
|
||||||
b.BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
sp.ID AS ServicePointID,
|
||||||
OR b.ID IN (
|
sp.Name,
|
||||||
SELECT lt.BeaconID FROM lt_BeaconsID_BusinessesID lt
|
sp.BeaconMinor,
|
||||||
WHERE lt.BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
sp.IsActive,
|
||||||
)
|
sp.TypeID
|
||||||
)
|
FROM ServicePoints sp
|
||||||
|
WHERE sp.BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
||||||
|
AND sp.BeaconMinor IS NOT NULL
|
||||||
<cfif onlyActive>
|
<cfif onlyActive>
|
||||||
AND b.IsActive = 1
|
AND sp.IsActive = 1
|
||||||
</cfif>
|
</cfif>
|
||||||
ORDER BY b.Name, b.ID
|
ORDER BY sp.BeaconMinor, sp.Name
|
||||||
</cfquery>
|
</cfquery>
|
||||||
|
|
||||||
<cfset beacons = []>
|
<cfset beacons = []>
|
||||||
<cfloop query="q">
|
<cfloop query="qSP">
|
||||||
<cfset arrayAppend(beacons, {
|
<cfset arrayAppend(beacons, {
|
||||||
"BeaconID" = q.ID,
|
"ServicePointID" = qSP.ServicePointID,
|
||||||
"BusinessID" = q.BusinessID,
|
"BusinessID" = bizId,
|
||||||
"Name" = q.Name,
|
"Name" = qSP.Name,
|
||||||
"UUID" = q.UUID,
|
"UUID" = shardInfo.ShardUUID,
|
||||||
"IsActive" = q.IsActive,
|
"Major" = shardInfo.Major,
|
||||||
"IsSharding" = false
|
"Minor" = qSP.BeaconMinor,
|
||||||
|
"IsActive" = qSP.IsActive ? true : false,
|
||||||
|
"TypeID" = qSP.TypeID
|
||||||
})>
|
})>
|
||||||
</cfloop>
|
</cfloop>
|
||||||
|
|
||||||
<!--- If using sharding, also show service points with BeaconMinor as "beacons" --->
|
|
||||||
<cfif usesSharding>
|
|
||||||
<cfquery name="qShardBeacons" datasource="payfrit">
|
|
||||||
SELECT
|
|
||||||
sp.ID AS ServicePointID,
|
|
||||||
sp.Name,
|
|
||||||
sp.BeaconMinor,
|
|
||||||
sp.IsActive
|
|
||||||
FROM ServicePoints sp
|
|
||||||
WHERE sp.BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
|
||||||
AND sp.BeaconMinor IS NOT NULL
|
|
||||||
<cfif onlyActive>
|
|
||||||
AND sp.IsActive = 1
|
|
||||||
</cfif>
|
|
||||||
ORDER BY sp.BeaconMinor, sp.Name
|
|
||||||
</cfquery>
|
|
||||||
|
|
||||||
<cfloop query="qShardBeacons">
|
|
||||||
<cfset arrayAppend(beacons, {
|
|
||||||
"BeaconID" = 0,
|
|
||||||
"ServicePointID" = qShardBeacons.ServicePointID,
|
|
||||||
"BusinessID" = bizId,
|
|
||||||
"Name" = qShardBeacons.Name,
|
|
||||||
"UUID" = shardingInfo.ShardUUID,
|
|
||||||
"Major" = shardingInfo.Major,
|
|
||||||
"Minor" = qShardBeacons.BeaconMinor,
|
|
||||||
"IsActive" = qShardBeacons.IsActive,
|
|
||||||
"IsSharding" = true
|
|
||||||
})>
|
|
||||||
</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,
|
||||||
COUNT=arrayLen(beacons),
|
BusinessName = qBiz.Name,
|
||||||
BEACONS=beacons,
|
COUNT = arrayLen(beacons),
|
||||||
USES_SHARDING=usesSharding,
|
BEACONS = beacons,
|
||||||
SHARDING_INFO=shardingInfo
|
HAS_SHARD = hasShard,
|
||||||
|
SHARD_INFO = shardInfo
|
||||||
})#</cfoutput>
|
})#</cfoutput>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -6,95 +6,10 @@
|
||||||
|
|
||||||
<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 structKeyExists(requestData, "Beacons") AND isArray(requestData.Beacons) AND arrayLen(requestData.Beacons) GT 0>
|
|
||||||
<cfset beacons = []>
|
|
||||||
|
|
||||||
<cfloop array="#requestData.Beacons#" index="beaconData">
|
<cfif NOT structKeyExists(requestData, "Beacons") OR NOT isArray(requestData.Beacons) OR arrayLen(requestData.Beacons) EQ 0>
|
||||||
<cfset uuid = "">
|
|
||||||
<cfset major = 0>
|
|
||||||
<cfset minor = -1>
|
|
||||||
|
|
||||||
<!--- Extract UUID (normalize: remove dashes, add back in standard format) --->
|
|
||||||
<cfif structKeyExists(beaconData, "UUID")>
|
|
||||||
<cfset rawUuid = uCase(reReplace(beaconData.UUID, "-", "", "all"))>
|
|
||||||
<cfif len(rawUuid) EQ 32>
|
|
||||||
<!--- Convert to standard UUID format with dashes for DB lookup --->
|
|
||||||
<cfset uuid = lCase(mid(rawUuid,1,8) & "-" & mid(rawUuid,9,4) & "-" & mid(rawUuid,13,4) & "-" & mid(rawUuid,17,4) & "-" & mid(rawUuid,21,12))>
|
|
||||||
</cfif>
|
|
||||||
</cfif>
|
|
||||||
|
|
||||||
<cfif structKeyExists(beaconData, "Major") AND isNumeric(beaconData.Major)>
|
|
||||||
<cfset major = int(beaconData.Major)>
|
|
||||||
</cfif>
|
|
||||||
|
|
||||||
<cfif structKeyExists(beaconData, "Minor") AND isNumeric(beaconData.Minor)>
|
|
||||||
<cfset minor = int(beaconData.Minor)>
|
|
||||||
</cfif>
|
|
||||||
|
|
||||||
<cfif len(uuid) EQ 0 OR major LT 0 OR minor LT 0>
|
|
||||||
<cfcontinue>
|
|
||||||
</cfif>
|
|
||||||
|
|
||||||
<!--- Resolve via sharding: Namespace (first 20 chars of 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">
|
|
||||||
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 = <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 Businesses parent ON biz.ParentBusinessID = parent.ID
|
|
||||||
WHERE LEFT(REPLACE(bs.UUID, '-', ''), 20) = <cfqueryparam cfsqltype="cf_sql_varchar" value="#namespace#">
|
|
||||||
AND bs.IsActive = 1
|
|
||||||
AND biz.IsDemo = 0
|
|
||||||
AND biz.IsPrivate = 0
|
|
||||||
LIMIT 1
|
|
||||||
</cfquery>
|
|
||||||
|
|
||||||
<cfif qShard.recordCount GT 0>
|
|
||||||
<cfset arrayAppend(beacons, {
|
|
||||||
"UUID" = uCase(reReplace(uuid, "-", "", "all")),
|
|
||||||
"Major" = major,
|
|
||||||
"Minor" = minor,
|
|
||||||
"BeaconID" = 0,
|
|
||||||
"BeaconName" = qShard.ServicePointName,
|
|
||||||
"BusinessID" = qShard.BusinessID,
|
|
||||||
"BusinessName" = qShard.BusinessName,
|
|
||||||
"ServicePointID" = val(qShard.ServicePointID),
|
|
||||||
"ServicePointName" = qShard.ServicePointName,
|
|
||||||
"ParentBusinessID" = val(qShard.ParentBusinessID),
|
|
||||||
"ParentBusinessName" = qShard.ParentBusinessName,
|
|
||||||
"HasChildren" = qShard.ChildCount GT 0
|
|
||||||
})>
|
|
||||||
</cfif>
|
|
||||||
</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({
|
<cfoutput>#serializeJSON({
|
||||||
"OK" = true,
|
"OK" = true,
|
||||||
"ERROR" = "",
|
"ERROR" = "",
|
||||||
|
|
@ -103,62 +18,69 @@
|
||||||
<cfabort>
|
<cfabort>
|
||||||
</cfif>
|
</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 = []>
|
<cfset beacons = []>
|
||||||
<cfloop query="qBeacons">
|
|
||||||
<cfset arrayAppend(beacons, {
|
<cfloop array="#requestData.Beacons#" index="beaconData">
|
||||||
"UUID" = qBeacons.UUID,
|
<cfset uuid = "">
|
||||||
"BeaconID" = qBeacons.BeaconID,
|
<cfset major = 0>
|
||||||
"BeaconName" = qBeacons.BeaconName,
|
<cfset minor = -1>
|
||||||
"BusinessID" = qBeacons.BusinessID,
|
|
||||||
"BusinessName" = qBeacons.BusinessName,
|
<!--- Extract UUID (normalize: remove dashes, add back in standard format) --->
|
||||||
"ServicePointID" = qBeacons.ServicePointID,
|
<cfif structKeyExists(beaconData, "UUID")>
|
||||||
"ServicePointName" = qBeacons.ServicePointName,
|
<cfset rawUuid = uCase(reReplace(beaconData.UUID, "-", "", "all"))>
|
||||||
"ParentBusinessID" = val(qBeacons.ParentBusinessID),
|
<cfif len(rawUuid) EQ 32>
|
||||||
"ParentBusinessName" = qBeacons.ParentBusinessName,
|
<!--- Convert to standard UUID format with dashes for DB lookup --->
|
||||||
"HasChildren" = qBeacons.ChildCount GT 0
|
<cfset uuid = lCase(mid(rawUuid,1,8) & "-" & mid(rawUuid,9,4) & "-" & mid(rawUuid,13,4) & "-" & mid(rawUuid,17,4) & "-" & mid(rawUuid,21,12))>
|
||||||
})>
|
</cfif>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif structKeyExists(beaconData, "Major") AND isNumeric(beaconData.Major)>
|
||||||
|
<cfset major = int(beaconData.Major)>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif structKeyExists(beaconData, "Minor") AND isNumeric(beaconData.Minor)>
|
||||||
|
<cfset minor = int(beaconData.Minor)>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<cfif len(uuid) EQ 0 OR major LT 0 OR minor LT 0>
|
||||||
|
<cfcontinue>
|
||||||
|
</cfif>
|
||||||
|
|
||||||
|
<!--- Resolve via sharding: UUID -> Shard -> Business (Major) -> ServicePoint (Minor) --->
|
||||||
|
<cfquery name="qShard" datasource="payfrit">
|
||||||
|
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 = <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 Businesses parent ON biz.ParentBusinessID = parent.ID
|
||||||
|
WHERE bs.UUID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#uuid#">
|
||||||
|
AND bs.IsActive = 1
|
||||||
|
AND biz.IsDemo = 0
|
||||||
|
AND biz.IsPrivate = 0
|
||||||
|
LIMIT 1
|
||||||
|
</cfquery>
|
||||||
|
|
||||||
|
<cfif qShard.recordCount GT 0>
|
||||||
|
<cfset arrayAppend(beacons, {
|
||||||
|
"UUID" = uCase(reReplace(uuid, "-", "", "all")),
|
||||||
|
"Major" = major,
|
||||||
|
"Minor" = minor,
|
||||||
|
"BusinessID" = qShard.BusinessID,
|
||||||
|
"BusinessName" = qShard.BusinessName,
|
||||||
|
"ServicePointID" = val(qShard.ServicePointID),
|
||||||
|
"ServicePointName" = qShard.ServicePointName,
|
||||||
|
"ParentBusinessID" = val(qShard.ParentBusinessID),
|
||||||
|
"ParentBusinessName" = qShard.ParentBusinessName,
|
||||||
|
"HasChildren" = qShard.ChildCount GT 0
|
||||||
|
})>
|
||||||
|
</cfif>
|
||||||
</cfloop>
|
</cfloop>
|
||||||
|
|
||||||
<cfoutput>#serializeJSON({
|
<cfoutput>#serializeJSON({
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
Reference in a new issue