payfrit-works/api/Application.cfm
John Mizerek 97a2621705 Add KDS (Kitchen Display System) with real-time order management
- Created listForKDS.cfm API endpoint for retrieving active orders
  - Filters by BusinessID and optional ServicePointID
  - Returns orders with StatusID 1-3 (New, Preparing, Ready)
  - Includes nested line items with item names
  - Joins customer and service point information

- Created updateStatus.cfm API endpoint for order status transitions
  - Updates OrderStatusID (New -> Preparing -> Ready -> Completed)
  - Updates OrderLastEditedOn timestamp
  - Validates order exists before updating

- Built complete HTML/JavaScript KDS web application
  - Real-time order display with configurable auto-refresh (default 5s)
  - Color-coded status cards: Blue (New), Orange (Preparing), Green (Ready)
  - Elapsed time tracking with visual warnings at 10min and 15min
  - Hierarchical line item display showing root items with nested modifiers
  - One-click status progression buttons
  - Persistent configuration (BusinessID, ServicePointID filter, refresh interval)
  - Responsive grid layout for multiple concurrent orders
  - Dark theme optimized for kitchen environments

- Updated Application.cfm to allow public access to KDS endpoints

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2025-12-29 17:46:01 -08:00

118 lines
4.2 KiB
Text

<cfsetting showdebugoutput="false">
<cfsetting enablecfoutputonly="true">
<!---
Payfrit API Application.cfm
FIX: Provide a DEFAULT datasource so endpoints can call queryExecute()
without specifying { datasource="payfrit" } every time.
Token-auth gate for /api endpoints.
Public allowlist (NO auth):
- /api/login.cfm
- /api/logout.cfm
- /api/auth/login.cfm
- /api/auth/logout.cfm
- /api/businesses/list.cfm
- /api/servicepoints/list.cfm
Authenticated requests should send:
- Header: X-User-Token: <token from /api/auth/login.cfm>
- Header: X-Business-ID: <selected BusinessID> (required for most endpoints)
--->
<!--- OPTION A: default datasource for the whole app (THIS fixes "missing datasource") --->
<cfapplication
name="payfrit_api"
sessionmanagement="true"
clientmanagement="false"
setclientcookies="false"
datasource="payfrit"
>
<cfscript>
function apiAbort(payload) {
writeOutput(serializeJSON(payload));
abort;
}
function headerValue(name) {
k = "HTTP_" & ucase(reReplace(arguments.name, "[^A-Za-z0-9]", "_", "all"));
if (structKeyExists(cgi, k)) return trim(cgi[k]);
return "";
}
// Determine request path
request._api_scriptName = "";
if (structKeyExists(cgi, "SCRIPT_NAME")) {
request._api_scriptName = cgi.SCRIPT_NAME;
} else if (structKeyExists(cgi, "PATH_INFO")) {
request._api_scriptName = cgi.PATH_INFO;
}
request._api_path = lcase(request._api_scriptName);
// Public allowlist
request._api_isPublic = false;
if (len(request._api_path)) {
if (findNoCase("/api/login.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/logout.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/auth/login.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/auth/logout.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/businesses/list.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/servicepoints/list.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/menu/items.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/orders/getOrCreateCart.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/orders/getCart.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/orders/setLineItem.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/orders/submit.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/orders/listForKDS.cfm", request._api_path)) request._api_isPublic = true;
if (findNoCase("/api/orders/updateStatus.cfm", request._api_path)) request._api_isPublic = true;
}
// Carry session values into request (if present)
if (!structKeyExists(request, "UserID") && structKeyExists(session, "UserID")) {
request.UserID = Duplicate(session.UserID);
}
if (!structKeyExists(request, "BusinessID") && structKeyExists(session, "BusinessID")) {
request.BusinessID = Duplicate(session.BusinessID);
}
// Token auth: X-User-Token -> request.UserID
request._api_userToken = headerValue("X-User-Token");
if (len(request._api_userToken)) {
try {
request._api_qTok = queryExecute(
"SELECT UserID FROM UserTokens WHERE Token = ? LIMIT 1",
[ { value = request._api_userToken, cfsqltype = "cf_sql_varchar" } ],
{ datasource = "payfrit" }
);
if (request._api_qTok.recordCount EQ 1) {
request.UserID = request._api_qTok.UserID;
session.UserID = request._api_qTok.UserID;
}
} catch (any e) {
// ignore; treated as unauthenticated
}
}
// Business header: X-Business-ID -> request.BusinessID
request._api_hdrBiz = headerValue("X-Business-ID");
if (len(request._api_hdrBiz) && isNumeric(request._api_hdrBiz)) {
request.BusinessID = int(request._api_hdrBiz);
session.BusinessID = request.BusinessID;
}
// Enforce auth (except public)
if (!request._api_isPublic) {
if (!structKeyExists(request, "UserID") || !isNumeric(request.UserID) || request.UserID LTE 0) {
apiAbort({ "OK": false, "ERROR": "not_logged_in" });
}
if (!structKeyExists(request, "BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0) {
apiAbort({ "OK": false, "ERROR": "no_business_selected" });
}
}
</cfscript>