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" });
|
||||
}
|
||||
|
||||
// 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")) {
|
|||
}
|
||||
</cfscript>
|
||||
|
||||
<!--- Check if this business uses beacon sharding --->
|
||||
<!--- Get business shard info --->
|
||||
<cfquery name="qBiz" datasource="payfrit">
|
||||
SELECT BeaconShardID, BeaconMajor
|
||||
FROM Businesses
|
||||
WHERE ID = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
||||
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 = <cfqueryparam cfsqltype="cf_sql_integer" value="#bizId#">
|
||||
LIMIT 1
|
||||
</cfquery>
|
||||
|
||||
<cfset usesSharding = qBiz.recordCount GT 0 AND val(qBiz.BeaconShardID) GT 0>
|
||||
<cfset shardingInfo = {}>
|
||||
|
||||
<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 qBiz.recordCount EQ 0>
|
||||
<cfoutput>#serializeJSON({ OK=false, ERROR="business_not_found" })#</cfoutput>
|
||||
<cfabort>
|
||||
</cfif>
|
||||
|
||||
<!--- Legacy beacons from Beacons table --->
|
||||
<cfquery name="q" 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#">
|
||||
)
|
||||
)
|
||||
<cfset hasShard = val(qBiz.BeaconShardID) GT 0>
|
||||
<cfset shardInfo = {
|
||||
"ShardID" = hasShard ? qBiz.BeaconShardID : 0,
|
||||
"ShardUUID" = hasShard ? qBiz.ShardUUID : "",
|
||||
"Major" = hasShard ? qBiz.BeaconMajor : 0
|
||||
}>
|
||||
|
||||
<!--- Get service points with beacon minor assignments --->
|
||||
<cfquery name="qSP" datasource="payfrit">
|
||||
SELECT
|
||||
sp.ID AS ServicePointID,
|
||||
sp.Name,
|
||||
sp.BeaconMinor,
|
||||
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>
|
||||
AND b.IsActive = 1
|
||||
AND sp.IsActive = 1
|
||||
</cfif>
|
||||
ORDER BY b.Name, b.ID
|
||||
ORDER BY sp.BeaconMinor, sp.Name
|
||||
</cfquery>
|
||||
|
||||
<cfset beacons = []>
|
||||
<cfloop query="q">
|
||||
<cfloop query="qSP">
|
||||
<cfset arrayAppend(beacons, {
|
||||
"BeaconID" = q.ID,
|
||||
"BusinessID" = q.BusinessID,
|
||||
"Name" = q.Name,
|
||||
"UUID" = q.UUID,
|
||||
"IsActive" = q.IsActive,
|
||||
"IsSharding" = false
|
||||
"ServicePointID" = qSP.ServicePointID,
|
||||
"BusinessID" = bizId,
|
||||
"Name" = qSP.Name,
|
||||
"UUID" = shardInfo.ShardUUID,
|
||||
"Major" = shardInfo.Major,
|
||||
"Minor" = qSP.BeaconMinor,
|
||||
"IsActive" = qSP.IsActive ? true : false,
|
||||
"TypeID" = qSP.TypeID
|
||||
})>
|
||||
</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>
|
||||
<cfoutput>#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
|
||||
})#</cfoutput>
|
||||
|
|
|
|||
|
|
@ -4,20 +4,11 @@
|
|||
<cfcontent type="application/json; charset=utf-8" reset="true">
|
||||
<cfheader name="Cache-Control" value="no-store">
|
||||
|
||||
<cfscript>
|
||||
function apiAbort(obj) {
|
||||
writeOutput(serializeJSON(obj));
|
||||
abort;
|
||||
}
|
||||
</cfscript>
|
||||
|
||||
<!--- No auth required - this is public for beacon scanning before login --->
|
||||
<!--- No auth required - returns all Payfrit shard UUIDs for beacon scanning --->
|
||||
|
||||
<cfquery name="q" datasource="payfrit">
|
||||
SELECT
|
||||
ID,
|
||||
UUID
|
||||
FROM Beacons
|
||||
SELECT ID, UUID
|
||||
FROM BeaconShards
|
||||
WHERE IsActive = 1
|
||||
ORDER BY ID
|
||||
</cfquery>
|
||||
|
|
@ -25,7 +16,7 @@ function apiAbort(obj) {
|
|||
<cfset items = []>
|
||||
<cfloop query="q">
|
||||
<cfset arrayAppend(items, {
|
||||
"BeaconID" = q.ID,
|
||||
"ShardID" = q.ID,
|
||||
"UUID" = q.UUID
|
||||
})>
|
||||
</cfloop>
|
||||
|
|
@ -33,5 +24,6 @@ function apiAbort(obj) {
|
|||
<cfoutput>#serializeJSON({
|
||||
"OK" = true,
|
||||
"ERROR" = "",
|
||||
"SHARDS" = items,
|
||||
"ITEMS" = items
|
||||
})#</cfoutput>
|
||||
|
|
|
|||
|
|
@ -6,95 +6,10 @@
|
|||
|
||||
<cfset requestData = deserializeJSON(toString(getHttpRequestData().content))>
|
||||
|
||||
<!--- ============================================================================
|
||||
SHARDING FORMAT: { "Beacons": [{ "UUID": "...", "Major": 49, "Minor": 15 }] }
|
||||
Resolves via BeaconShards -> Businesses (BeaconMajor) -> ServicePoints (BeaconMinor)
|
||||
============================================================================ --->
|
||||
<cfif structKeyExists(requestData, "Beacons") AND isArray(requestData.Beacons) AND arrayLen(requestData.Beacons) GT 0>
|
||||
<cfset beacons = []>
|
||||
<!--- Beacon lookup via sharding: { "Beacons": [{ "UUID": "...", "Major": 49, "Minor": 15 }] }
|
||||
Resolves via BeaconShards -> Businesses (BeaconMajor) -> ServicePoints (BeaconMinor) --->
|
||||
|
||||
<cfloop array="#requestData.Beacons#" index="beaconData">
|
||||
<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>
|
||||
<cfif NOT structKeyExists(requestData, "Beacons") OR NOT isArray(requestData.Beacons) OR arrayLen(requestData.Beacons) EQ 0>
|
||||
<cfoutput>#serializeJSON({
|
||||
"OK" = true,
|
||||
"ERROR" = "",
|
||||
|
|
@ -103,62 +18,69 @@
|
|||
<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 array="#requestData.Beacons#" index="beaconData">
|
||||
<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: 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>
|
||||
|
||||
<cfoutput>#serializeJSON({
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}));
|
||||
</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">
|
||||
<cfheader statuscode="200" statustext="OK">
|
||||
<cfcontent type="application/json; charset=utf-8" reset="true">
|
||||
|
|
|
|||
Reference in a new issue