- New api/tabs/ directory with 13 endpoints: open, close, cancel, get, getActive, addOrder, increaseAuth, addMember, removeMember, getPresence, approveOrder, rejectOrder, pendingOrders - New api/presence/heartbeat.cfm for beacon-based user presence tracking - New cron/expireTabs.cfm for idle tab expiry and presence cleanup - Modified submit.cfm for tab-aware order submission (skip payment, update running total) - Modified getOrCreateCart.cfm to auto-detect active tab and set TabID on new carts - Modified webhook.cfm to handle tab capture events (metadata type=tab_close) - Modified businesses/get.cfm and updateTabs.cfm with new tab config columns - Updated portal tab settings UI with auth amounts, max members, approval toggle - Added tab and presence endpoints to Application.cfm public allowlist Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
88 lines
3.4 KiB
Text
88 lines
3.4 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<cfsetting enablecfoutputonly="true">
|
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
<cfheader name="Cache-Control" value="no-store">
|
|
|
|
<cfscript>
|
|
/**
|
|
* Get Presence at Service Point
|
|
* Returns users currently at the same business/service point.
|
|
* Used by tab owner to see who they can add.
|
|
*
|
|
* POST: { BusinessID: int, ServicePointID: int (optional), UserID: int (requesting user, excluded from results) }
|
|
*/
|
|
|
|
try {
|
|
requestData = deserializeJSON(toString(getHttpRequestData().content));
|
|
businessID = val(requestData.BusinessID ?: 0);
|
|
servicePointID = val(requestData.ServicePointID ?: 0);
|
|
userID = val(requestData.UserID ?: 0);
|
|
|
|
if (businessID == 0) apiAbort({ "OK": false, "ERROR": "missing_BusinessID" });
|
|
|
|
// Get users present at this business within last 30 minutes
|
|
// If servicePointID specified, filter to that SP; otherwise show all at the business
|
|
if (servicePointID > 0) {
|
|
qPresence = queryTimed("
|
|
SELECT up.UserID, up.ServicePointID, up.LastSeenOn,
|
|
u.FirstName, u.LastName, u.ImageExtension,
|
|
sp.Name AS ServicePointName
|
|
FROM UserPresence up
|
|
JOIN Users u ON u.ID = up.UserID
|
|
LEFT JOIN ServicePoints sp ON sp.ID = up.ServicePointID
|
|
WHERE up.BusinessID = :bizID
|
|
AND up.ServicePointID = :spID
|
|
AND up.LastSeenOn >= DATE_SUB(NOW(), INTERVAL 30 MINUTE)
|
|
AND up.UserID != :excludeUserID
|
|
ORDER BY up.LastSeenOn DESC
|
|
", {
|
|
bizID: { value: businessID, cfsqltype: "cf_sql_integer" },
|
|
spID: { value: servicePointID, cfsqltype: "cf_sql_integer" },
|
|
excludeUserID: { value: userID, cfsqltype: "cf_sql_integer" }
|
|
});
|
|
} else {
|
|
qPresence = queryTimed("
|
|
SELECT up.UserID, up.ServicePointID, up.LastSeenOn,
|
|
u.FirstName, u.LastName, u.ImageExtension,
|
|
sp.Name AS ServicePointName
|
|
FROM UserPresence up
|
|
JOIN Users u ON u.ID = up.UserID
|
|
LEFT JOIN ServicePoints sp ON sp.ID = up.ServicePointID
|
|
WHERE up.BusinessID = :bizID
|
|
AND up.LastSeenOn >= DATE_SUB(NOW(), INTERVAL 30 MINUTE)
|
|
AND up.UserID != :excludeUserID
|
|
ORDER BY up.LastSeenOn DESC
|
|
", {
|
|
bizID: { value: businessID, cfsqltype: "cf_sql_integer" },
|
|
excludeUserID: { value: userID, cfsqltype: "cf_sql_integer" }
|
|
});
|
|
}
|
|
|
|
users = [];
|
|
for (row in qPresence) {
|
|
// Check if user is already on a tab
|
|
qOnTab = queryTimed("
|
|
SELECT t.ID AS TabID FROM TabMembers tm
|
|
JOIN Tabs t ON t.ID = tm.TabID
|
|
WHERE tm.UserID = :uid AND tm.StatusID = 1 AND t.StatusID = 1
|
|
LIMIT 1
|
|
", { uid: { value: row.UserID, cfsqltype: "cf_sql_integer" } });
|
|
|
|
arrayAppend(users, {
|
|
"UserID": row.UserID,
|
|
"FirstName": row.FirstName,
|
|
"LastName": row.LastName,
|
|
"ImageExtension": row.ImageExtension ?: "",
|
|
"ServicePointID": val(row.ServicePointID),
|
|
"ServicePointName": row.ServicePointName ?: "",
|
|
"LastSeenOn": toISO8601(row.LastSeenOn),
|
|
"IsOnTab": qOnTab.recordCount > 0
|
|
});
|
|
}
|
|
|
|
apiAbort({ "OK": true, "USERS": users });
|
|
|
|
} catch (any e) {
|
|
apiAbort({ "OK": false, "ERROR": "server_error", "MESSAGE": e.message });
|
|
}
|
|
</cfscript>
|