Public businesses + servicepoints list endpoints; API allowlist; Lucee-safe JSON
This commit is contained in:
parent
10200ad140
commit
848544ba53
4 changed files with 289 additions and 113 deletions
|
|
@ -4,25 +4,20 @@
|
||||||
<!---
|
<!---
|
||||||
FILE: C:\lucee\tomcat\webapps\ROOT\biz.payfrit.com\api\Application.cfm
|
FILE: C:\lucee\tomcat\webapps\ROOT\biz.payfrit.com\api\Application.cfm
|
||||||
|
|
||||||
GOAL:
|
MVP CHANGE:
|
||||||
- Ensure /api/* requests run under the SAME CF application/session as biz.payfrit.com
|
- Public allowlist for:
|
||||||
by including the parent Application.cfm (because CF uses the nearest Application.cfm).
|
/api/businesses/list.cfm
|
||||||
- Force JSON responses for API requests (never HTML).
|
/api/servicepoints/list.cfm
|
||||||
- Gate access based on existing biz login + business "Become" selection:
|
|
||||||
session.UserID / request.UserID
|
IMPORTANT:
|
||||||
session.BusinessID / request.BusinessID
|
- Do NOT rely on exact SCRIPT_NAME equality; in some deployments it may include
|
||||||
|
the site folder prefix (e.g. /biz.payfrit.com/api/...).
|
||||||
|
- So we allowlist by "contains" match.
|
||||||
--->
|
--->
|
||||||
|
|
||||||
<!--- Mark API context BEFORE including parent, in case parent logic checks it --->
|
|
||||||
<cfset request.IsApiRequest = true>
|
<cfset request.IsApiRequest = true>
|
||||||
|
|
||||||
<!---
|
|
||||||
CRITICAL:
|
|
||||||
Without this include, /api/* becomes a different app/session and won't see logged-in state.
|
|
||||||
--->
|
|
||||||
<cfinclude template="../Application.cfm">
|
<cfinclude template="../Application.cfm">
|
||||||
|
|
||||||
<!--- Force JSON for everything under /api --->
|
|
||||||
<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">
|
||||||
|
|
||||||
|
|
@ -32,8 +27,28 @@ function apiAbort(obj) {
|
||||||
abort;
|
abort;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some apps store auth in session and copy to request in page code.
|
// Determine current request path
|
||||||
// Make /api resilient by copying from session if needed.
|
scriptName = "";
|
||||||
|
if (structKeyExists(cgi, "SCRIPT_NAME")) {
|
||||||
|
scriptName = cgi.SCRIPT_NAME;
|
||||||
|
} else if (structKeyExists(cgi, "PATH_INFO")) {
|
||||||
|
scriptName = cgi.PATH_INFO;
|
||||||
|
}
|
||||||
|
|
||||||
|
// MVP allowlist: PUBLIC endpoint(s) under /api
|
||||||
|
isPublicEndpoint = false;
|
||||||
|
if (len(scriptName)) {
|
||||||
|
// Use contains (case-insensitive) so it works whether SCRIPT_NAME is:
|
||||||
|
// /api/servicepoints/list.cfm OR /biz.payfrit.com/api/servicepoints/list.cfm
|
||||||
|
if (findNoCase("/api/businesses/list.cfm", scriptName) GT 0) {
|
||||||
|
isPublicEndpoint = true;
|
||||||
|
}
|
||||||
|
if (findNoCase("/api/servicepoints/list.cfm", scriptName) GT 0) {
|
||||||
|
isPublicEndpoint = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy auth from session if present
|
||||||
if (!structKeyExists(request, "UserID") && structKeyExists(session, "UserID")) {
|
if (!structKeyExists(request, "UserID") && structKeyExists(session, "UserID")) {
|
||||||
request.UserID = Duplicate(session.UserID);
|
request.UserID = Duplicate(session.UserID);
|
||||||
}
|
}
|
||||||
|
|
@ -41,11 +56,13 @@ if (!structKeyExists(request, "BusinessID") && structKeyExists(session, "Busines
|
||||||
request.BusinessID = Duplicate(session.BusinessID);
|
request.BusinessID = Duplicate(session.BusinessID);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Enforce auth for all /api endpoints
|
// Enforce auth for all /api endpoints EXCEPT allowlisted public endpoints
|
||||||
if (!structKeyExists(request, "UserID") || !isNumeric(request.UserID) || request.UserID LTE 0) {
|
if (!isPublicEndpoint) {
|
||||||
|
if (!structKeyExists(request, "UserID") || !isNumeric(request.UserID) || request.UserID LTE 0) {
|
||||||
apiAbort({ OK=false, ERROR="not_logged_in" });
|
apiAbort({ OK=false, ERROR="not_logged_in" });
|
||||||
}
|
}
|
||||||
if (!structKeyExists(request, "BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0) {
|
if (!structKeyExists(request, "BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0) {
|
||||||
apiAbort({ OK=false, ERROR="no_business_selected" });
|
apiAbort({ OK=false, ERROR="no_business_selected" });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</cfscript>
|
</cfscript>
|
||||||
|
|
|
||||||
|
|
@ -1,68 +1,91 @@
|
||||||
<cfsetting showdebugoutput="false">
|
<cfsetting showdebugoutput="false">
|
||||||
<cfsetting enablecfoutputonly="true">
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
||||||
<cfheader name="Cache-Control" value="no-store">
|
|
||||||
|
|
||||||
<cfscript>
|
<cfscript>
|
||||||
function apiAbort(obj){
|
/*
|
||||||
writeOutput(serializeJSON(obj));
|
FILE: C:\lucee\tomcat\webapps\ROOT\biz.payfrit.com\api\businesses\list.cfm
|
||||||
|
|
||||||
|
MVP:
|
||||||
|
- PUBLIC endpoint (no login required)
|
||||||
|
- Returns a minimal list of restaurants/businesses for the drilldown.
|
||||||
|
- IMPORTANT: Output JSON manually to preserve exact key casing.
|
||||||
|
|
||||||
|
RESPONSE (case-sensitive keys):
|
||||||
|
{
|
||||||
|
"OK": true,
|
||||||
|
"COUNT": 15,
|
||||||
|
"Businesses": [
|
||||||
|
{ "BusinessID": 12, "BusinessName": "Annie's Soul Delicious" },
|
||||||
|
...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
function apiAbort(obj) {
|
||||||
|
writeOutput('{');
|
||||||
|
writeOutput('"OK":' & (obj.OK ? 'true' : 'false'));
|
||||||
|
|
||||||
|
if (structKeyExists(obj, "ERROR")) {
|
||||||
|
writeOutput(',"ERROR":"' & encodeForJSON(toString(obj.ERROR)) & '"');
|
||||||
|
}
|
||||||
|
if (structKeyExists(obj, "DETAIL")) {
|
||||||
|
writeOutput(',"DETAIL":"' & encodeForJSON(toString(obj.DETAIL)) & '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOutput('}');
|
||||||
abort;
|
abort;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!structKeyExists(request,"UserID") || !isNumeric(request.UserID) || request.UserID LTE 0){
|
// NOTE: Do NOT use 'var' at top-level in a .cfm (local scope is function-only).
|
||||||
apiAbort({OK=false,ERROR="not_logged_in"});
|
dsn = "";
|
||||||
|
if (structKeyExists(request, "datasource") && len(trim(request.datasource))) {
|
||||||
|
dsn = trim(request.datasource);
|
||||||
|
} else if (structKeyExists(request, "dsn") && len(trim(request.dsn))) {
|
||||||
|
dsn = trim(request.dsn);
|
||||||
|
} else if (structKeyExists(application, "datasource") && len(trim(application.datasource))) {
|
||||||
|
dsn = trim(application.datasource);
|
||||||
|
} else if (structKeyExists(application, "dsn") && len(trim(application.dsn))) {
|
||||||
|
dsn = trim(application.dsn);
|
||||||
}
|
}
|
||||||
if (!structKeyExists(request,"BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0){
|
|
||||||
apiAbort({OK=false,ERROR="no_business_selected"});
|
|
||||||
}
|
|
||||||
</cfscript>
|
|
||||||
|
|
||||||
<cfquery name="q" datasource="#application.datasource#">
|
if (!len(dsn)) {
|
||||||
|
apiAbort({
|
||||||
|
OK=false,
|
||||||
|
ERROR="missing_datasource",
|
||||||
|
DETAIL="No datasource found in request.datasource, request.dsn, application.datasource, or application.dsn."
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
q = queryExecute(
|
||||||
|
"
|
||||||
SELECT
|
SELECT
|
||||||
lt.lt_Beacon_Businesses_ServicePointID AS lt_Beacon_Businesses_ServicePointID,
|
BusinessID AS BusinessID,
|
||||||
lt.BeaconID AS BeaconID,
|
BusinessName AS BusinessName
|
||||||
b.BeaconName AS BeaconName,
|
FROM Businesses
|
||||||
lt.ServicePointID AS ServicePointID,
|
ORDER BY BusinessName
|
||||||
sp.ServicePointName AS ServicePointName,
|
",
|
||||||
lt.lt_Beacon_Businesses_ServicePointNotes AS lt_Beacon_Businesses_ServicePointNotes,
|
[],
|
||||||
lt.CreatedAt AS CreatedAt
|
{ datasource = dsn }
|
||||||
FROM lt_Beacon_Businesses_ServicePoints lt
|
);
|
||||||
INNER JOIN Beacons b
|
|
||||||
ON b.BeaconID = lt.BeaconID
|
|
||||||
AND b.BusinessID = lt.BusinessID
|
|
||||||
INNER JOIN ServicePoints sp
|
|
||||||
ON sp.ServicePointID = lt.ServicePointID
|
|
||||||
AND sp.BusinessID = lt.BusinessID
|
|
||||||
WHERE lt.BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
|
|
||||||
ORDER BY lt.lt_Beacon_Businesses_ServicePointID DESC
|
|
||||||
</cfquery>
|
|
||||||
|
|
||||||
<cfscript>
|
countVal = q.recordCount;
|
||||||
assignments = [];
|
|
||||||
i = 1;
|
|
||||||
|
|
||||||
for (i = 1; i <= q.recordCount; i = i + 1){
|
writeOutput('{');
|
||||||
// Build keys EXACTLY as the admin UI expects (case-sensitive in JS)
|
writeOutput('"OK":true');
|
||||||
row = {
|
writeOutput(',"COUNT":' & countVal);
|
||||||
"lt_Beacon_Businesses_ServicePointID": q["lt_Beacon_Businesses_ServicePointID"][i],
|
writeOutput(',"Businesses":[');
|
||||||
"BeaconID": q["BeaconID"][i],
|
|
||||||
"BeaconName": q["BeaconName"][i],
|
for (i = 1; i LTE q.recordCount; i = i + 1) {
|
||||||
"ServicePointID": q["ServicePointID"][i],
|
if (i GT 1) writeOutput(',');
|
||||||
"ServicePointName": q["ServicePointName"][i],
|
|
||||||
"lt_Beacon_Businesses_ServicePointNotes": q["lt_Beacon_Businesses_ServicePointNotes"][i],
|
bid = q["BusinessID"][i];
|
||||||
"CreatedAt": q["CreatedAt"][i]
|
bname = q["BusinessName"][i];
|
||||||
};
|
|
||||||
arrayAppend(assignments, row);
|
writeOutput('{');
|
||||||
|
writeOutput('"BusinessID":' & int(bid));
|
||||||
|
writeOutput(',"BusinessName":"' & encodeForJSON(toString(bname)) & '"');
|
||||||
|
writeOutput('}');
|
||||||
}
|
}
|
||||||
|
|
||||||
out = {
|
writeOutput(']}');
|
||||||
"OK": true,
|
|
||||||
"ERROR": "",
|
|
||||||
"BUSINESSID": (request.BusinessID & ""),
|
|
||||||
"COUNT": arrayLen(assignments),
|
|
||||||
"ASSIGNMENTS": assignments
|
|
||||||
};
|
|
||||||
</cfscript>
|
</cfscript>
|
||||||
|
|
||||||
<cfoutput>#serializeJSON(out)#</cfoutput>
|
|
||||||
|
|
|
||||||
81
api/businesses/list.cfm
Normal file
81
api/businesses/list.cfm
Normal file
|
|
@ -0,0 +1,81 @@
|
||||||
|
<cfsetting showdebugoutput="false">
|
||||||
|
<cfsetting enablecfoutputonly="true">
|
||||||
|
|
||||||
|
<cfscript>
|
||||||
|
/*
|
||||||
|
PUBLIC MVP endpoint
|
||||||
|
Returns Businesses list with STRICT casing:
|
||||||
|
- Businesses
|
||||||
|
- BusinessID
|
||||||
|
- BusinessName
|
||||||
|
|
||||||
|
Lucee-safe JSON output (NO encodeForJSON)
|
||||||
|
*/
|
||||||
|
|
||||||
|
function jsonString(val) {
|
||||||
|
// serializeJSON("abc") -> "\"abc\""
|
||||||
|
// we strip the surrounding quotes
|
||||||
|
var s = serializeJSON(toString(val));
|
||||||
|
return mid(s, 2, len(s) - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
function apiAbort(obj) {
|
||||||
|
writeOutput('{');
|
||||||
|
writeOutput('"OK":false');
|
||||||
|
|
||||||
|
if (structKeyExists(obj, "ERROR")) {
|
||||||
|
writeOutput(',"ERROR":"' & jsonString(obj.ERROR) & '"');
|
||||||
|
}
|
||||||
|
if (structKeyExists(obj, "DETAIL")) {
|
||||||
|
writeOutput(',"DETAIL":"' & jsonString(obj.DETAIL) & '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOutput('}');
|
||||||
|
abort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- datasource resolution (unchanged) ---- */
|
||||||
|
dsn = "";
|
||||||
|
if (structKeyExists(application, "datasource")) {
|
||||||
|
dsn = application.datasource;
|
||||||
|
} else if (structKeyExists(application, "dsn")) {
|
||||||
|
dsn = application.dsn;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!len(dsn)) {
|
||||||
|
apiAbort({
|
||||||
|
ERROR = "missing_datasource",
|
||||||
|
DETAIL = "No datasource configured"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---- query ---- */
|
||||||
|
q = queryExecute(
|
||||||
|
"
|
||||||
|
SELECT
|
||||||
|
BusinessID,
|
||||||
|
BusinessName
|
||||||
|
FROM Businesses
|
||||||
|
ORDER BY BusinessName
|
||||||
|
",
|
||||||
|
[],
|
||||||
|
{ datasource = dsn }
|
||||||
|
);
|
||||||
|
|
||||||
|
/* ---- output ---- */
|
||||||
|
writeOutput('{');
|
||||||
|
writeOutput('"OK":true');
|
||||||
|
writeOutput(',"COUNT":' & q.recordCount);
|
||||||
|
writeOutput(',"Businesses":[');
|
||||||
|
|
||||||
|
for (i = 1; i LTE q.recordCount; i = i + 1) {
|
||||||
|
if (i GT 1) writeOutput(',');
|
||||||
|
|
||||||
|
writeOutput('{');
|
||||||
|
writeOutput('"BusinessID":' & q.BusinessID[i]);
|
||||||
|
writeOutput(',"BusinessName":"' & jsonString(q.BusinessName[i]) & '"');
|
||||||
|
writeOutput('}');
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOutput(']}');
|
||||||
|
</cfscript>
|
||||||
|
|
@ -5,32 +5,65 @@
|
||||||
<cfheader name="Cache-Control" value="no-store">
|
<cfheader name="Cache-Control" value="no-store">
|
||||||
|
|
||||||
<cfscript>
|
<cfscript>
|
||||||
|
/*
|
||||||
|
FILE: C:\lucee\tomcat\webapps\ROOT\biz.payfrit.com\api\servicepoints\list.cfm
|
||||||
|
|
||||||
|
MVP:
|
||||||
|
- PUBLIC (no login required)
|
||||||
|
- Reads BusinessID from JSON body
|
||||||
|
- Optional: onlyActive (default true)
|
||||||
|
- Returns hump-case JSON:
|
||||||
|
OK, ERROR, BusinessID, COUNT, ServicePoints
|
||||||
|
*/
|
||||||
|
|
||||||
|
function jsonString(val) {
|
||||||
|
// Lucee-safe string escaping:
|
||||||
|
// serializeJSON("abc") -> "\"abc\"" ; strip surrounding quotes
|
||||||
|
s = serializeJSON(toString(val));
|
||||||
|
return mid(s, 2, len(s) - 2);
|
||||||
|
}
|
||||||
|
|
||||||
function apiAbort(obj) {
|
function apiAbort(obj) {
|
||||||
writeOutput(serializeJSON(obj));
|
writeOutput('{');
|
||||||
|
writeOutput('"OK":false');
|
||||||
|
|
||||||
|
if (structKeyExists(obj, "ERROR")) {
|
||||||
|
writeOutput(',"ERROR":"' & jsonString(obj.ERROR) & '"');
|
||||||
|
}
|
||||||
|
if (structKeyExists(obj, "MESSAGE")) {
|
||||||
|
writeOutput(',"MESSAGE":"' & jsonString(obj.MESSAGE) & '"');
|
||||||
|
}
|
||||||
|
if (structKeyExists(obj, "DETAIL")) {
|
||||||
|
writeOutput(',"DETAIL":"' & jsonString(obj.DETAIL) & '"');
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOutput('}');
|
||||||
abort;
|
abort;
|
||||||
}
|
}
|
||||||
|
|
||||||
function readJsonBody() {
|
function readJsonBody() {
|
||||||
raw = toString(getHttpRequestData().content);
|
raw = toString(getHttpRequestData().content);
|
||||||
if (isNull(raw) || len(trim(raw)) EQ 0) return {};
|
if (isNull(raw) || len(trim(raw)) EQ 0) return {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
parsed = deserializeJSON(raw);
|
parsed = deserializeJSON(raw);
|
||||||
} catch(any e) {
|
} catch(any e) {
|
||||||
apiAbort({ OK=false, ERROR="bad_json", MESSAGE="Invalid JSON body" });
|
apiAbort({ ERROR="bad_json", MESSAGE="Invalid JSON body" });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isStruct(parsed)) return {};
|
if (!isStruct(parsed)) return {};
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
data = readJsonBody();
|
data = readJsonBody();
|
||||||
|
|
||||||
if (!structKeyExists(request, "UserID") || !isNumeric(request.UserID) || request.UserID LTE 0) {
|
// Require BusinessID in JSON body for public MVP endpoint
|
||||||
apiAbort({ OK=false, ERROR="not_logged_in" });
|
if (!structKeyExists(data, "BusinessID") || !isNumeric(data.BusinessID) || int(data.BusinessID) LTE 0) {
|
||||||
}
|
apiAbort({ ERROR="missing_businessid", MESSAGE="Body must include numeric BusinessID" });
|
||||||
if (!structKeyExists(request, "BusinessID") || !isNumeric(request.BusinessID) || request.BusinessID LTE 0) {
|
|
||||||
apiAbort({ OK=false, ERROR="no_business_selected" });
|
|
||||||
}
|
}
|
||||||
|
BusinessID = int(data.BusinessID);
|
||||||
|
|
||||||
|
// Default: only active service points 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)) {
|
||||||
|
|
@ -41,42 +74,64 @@ if (structKeyExists(data, "onlyActive")) {
|
||||||
onlyActive = (lcase(trim(toString(data.onlyActive))) EQ "true");
|
onlyActive = (lcase(trim(toString(data.onlyActive))) EQ "true");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Datasource (use existing app config)
|
||||||
|
dsn = "";
|
||||||
|
if (structKeyExists(application, "datasource") && len(trim(toString(application.datasource)))) {
|
||||||
|
dsn = trim(toString(application.datasource));
|
||||||
|
} else if (structKeyExists(application, "dsn") && len(trim(toString(application.dsn)))) {
|
||||||
|
dsn = trim(toString(application.dsn));
|
||||||
|
}
|
||||||
|
if (!len(dsn)) {
|
||||||
|
apiAbort({ ERROR="missing_datasource", MESSAGE="application.datasource is not set" });
|
||||||
|
}
|
||||||
</cfscript>
|
</cfscript>
|
||||||
|
|
||||||
<cfquery name="q" datasource="#application.datasource#">
|
<cfquery name="q" datasource="#dsn#">
|
||||||
SELECT
|
SELECT
|
||||||
ServicePointID,
|
ServicePointID,
|
||||||
BusinessID,
|
ServicePointBusinessID,
|
||||||
ServicePointName,
|
ServicePointName,
|
||||||
ServicePointTypeID,
|
ServicePointTypeID,
|
||||||
ServicePointCode,
|
ServicePointCode,
|
||||||
Description,
|
ServicePointDescription,
|
||||||
SortOrder,
|
ServicePointSortOrder,
|
||||||
IsActive,
|
ServicePointIsActive,
|
||||||
CreatedAt,
|
ServicePointCreatedAt,
|
||||||
UpdatedAt
|
ServicePointUpdatedAt
|
||||||
FROM ServicePoints
|
FROM ServicePoints
|
||||||
WHERE BusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#request.BusinessID#">
|
WHERE ServicePointBusinessID = <cfqueryparam cfsqltype="cf_sql_integer" value="#BusinessID#">
|
||||||
<cfif onlyActive>
|
<cfif onlyActive>
|
||||||
AND IsActive = 1
|
AND ServicePointIsActive = 1
|
||||||
</cfif>
|
</cfif>
|
||||||
ORDER BY SortOrder, ServicePointName, ServicePointID
|
ORDER BY ServicePointSortOrder, ServicePointName, ServicePointID
|
||||||
</cfquery>
|
</cfquery>
|
||||||
|
|
||||||
<cfset sps = []>
|
<cfscript>
|
||||||
<cfloop query="q">
|
// Manual JSON output for exact key casing
|
||||||
<cfset arrayAppend(sps, {
|
writeOutput('{');
|
||||||
"ServicePointID"=q.ServicePointID,
|
writeOutput('"OK":true');
|
||||||
"BusinessID"=q.BusinessID,
|
writeOutput(',"ERROR":""');
|
||||||
"ServicePointName"=q.ServicePointName,
|
writeOutput(',"BusinessID":' & BusinessID);
|
||||||
"ServicePointTypeID"=q.ServicePointTypeID,
|
writeOutput(',"COUNT":' & q.recordCount);
|
||||||
"ServicePointCode"=q.ServicePointCode,
|
writeOutput(',"ServicePoints":[');
|
||||||
"Description"=q.Description,
|
|
||||||
"SortOrder"=q.SortOrder,
|
|
||||||
"IsActive"=q.IsActive,
|
|
||||||
"CreatedAt"=(q.CreatedAt & ""),
|
|
||||||
"UpdatedAt"=(q.UpdatedAt & "")
|
|
||||||
})>
|
|
||||||
</cfloop>
|
|
||||||
|
|
||||||
<cfoutput>#serializeJSON({ OK=true, ERROR="", BUSINESSID=(request.BusinessID & ""), COUNT=arrayLen(sps), SERVICEPOINTS=sps })#</cfoutput>
|
for (i = 1; i LTE q.recordCount; i = i + 1) {
|
||||||
|
if (i GT 1) writeOutput(',');
|
||||||
|
|
||||||
|
writeOutput('{');
|
||||||
|
writeOutput('"ServicePointID":' & q.ServicePointID[i]);
|
||||||
|
writeOutput(',"BusinessID":' & q.ServicePointBusinessID[i]);
|
||||||
|
writeOutput(',"ServicePointName":"' & jsonString(q.ServicePointName[i]) & '"');
|
||||||
|
writeOutput(',"ServicePointTypeID":' & q.ServicePointTypeID[i]);
|
||||||
|
writeOutput(',"ServicePointCode":' & (isNull(q.ServicePointCode[i]) ? 'null' : '"' & jsonString(q.ServicePointCode[i]) & '"'));
|
||||||
|
writeOutput(',"ServicePointDescription":' & (isNull(q.ServicePointDescription[i]) ? 'null' : '"' & jsonString(q.ServicePointDescription[i]) & '"'));
|
||||||
|
writeOutput(',"ServicePointSortOrder":' & q.ServicePointSortOrder[i]);
|
||||||
|
writeOutput(',"ServicePointIsActive":' & q.ServicePointIsActive[i]);
|
||||||
|
writeOutput(',"ServicePointCreatedAt":"' & jsonString(q.ServicePointCreatedAt[i] & "") & '"');
|
||||||
|
writeOutput(',"ServicePointUpdatedAt":"' & jsonString(q.ServicePointUpdatedAt[i] & "") & '"');
|
||||||
|
writeOutput('}');
|
||||||
|
}
|
||||||
|
|
||||||
|
writeOutput(']}');
|
||||||
|
</cfscript>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue