This repository has been archived on 2026-03-21. You can view files and clone it, but cannot push or open issues or pull requests.
payfrit-biz/api/config/environment.cfm
John Mizerek 3a9f952d8b Fix Toast __OO_STATE__ extraction: remove tag stripping and var keywords
Tag stripping via reReplace on 1.7M HTML was likely causing the silent
failure on biz server. Brace-counting doesn't need tag stripping since
HTML tags don't contain { or } and attribute quotes come in balanced
pairs. Also removed var keywords from page-level cfscript (may not be
supported in Lucee at template level) and added detailed error output
to the cfcatch for debugging.

Also auto-detect dev/prod environment from hostname instead of
hardcoded flag.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-01 20:28:02 -08:00

265 lines
9.1 KiB
Text

<cfscript>
/**
* Environment Configuration
*
* Auto-detects dev vs production based on server hostname.
* No manual flag to change — safe to deploy via git pull to either server.
*/
// ============================================
// ENVIRONMENT FLAG - AUTO-DETECTED
// ============================================
isDevEnvironment = (createObject("java", "java.net.InetAddress").getLocalHost().getHostName() != "biz");
// ============================================
// ERROR HANDLING
// ============================================
// Dev: Show full stack traces
// Prod: Show generic "server error" message
application.showDetailedErrors = isDevEnvironment;
// ============================================
// DEBUG LOGGING
// ============================================
// Dev: Log all API requests/responses
// Prod: Minimal logging
application.debugLogging = isDevEnvironment;
application.logDirectory = expandPath("/api/logs/");
// ============================================
// RATE LIMITING
// ============================================
// Dev: No rate limits
// Prod: Enforce rate limits
application.enableRateLimiting = !isDevEnvironment;
application.rateLimitPerMinute = 60; // requests per minute per IP
// ============================================
// EMAIL HANDLING
// ============================================
// Dev: Send all emails to catch-all address
// Prod: Send to real recipients
application.emailCatchAll = isDevEnvironment ? "dev-emails@payfrit.com" : "";
application.emailEnabled = !isDevEnvironment ? true : false; // Disable emails on dev entirely
// ============================================
// MAGIC OTP (for testing)
// ============================================
// Dev: Allow magic phone number to bypass OTP
// Prod: Disabled
application.MAGIC_OTP_ENABLED = isDevEnvironment;
application.MAGIC_OTP_CODE = "123456";
application.MAGIC_PHONE_NUMBERS = ["5555555555", "0000000000"];
// ============================================
// STRIPE MODE
// ============================================
// Already handled in stripe.cfm, but good to have reference
application.stripeTestMode = isDevEnvironment;
// ============================================
// API RESPONSE EXTRAS
// ============================================
// Dev: Include debug info in API responses (timing, queries, etc)
// Prod: Minimal responses
application.includeDebugInResponse = isDevEnvironment;
// ============================================
// SESSION/TOKEN SETTINGS
// ============================================
// Dev: Longer token expiry for easier testing
// Prod: Normal expiry
application.tokenExpiryHours = isDevEnvironment ? 720 : 24; // 30 days vs 1 day
// ============================================
// HELPER FUNCTIONS
// ============================================
function isDev() {
return structKeyExists(application, "isDevEnvironment") && application.isDevEnvironment;
}
function logDebug(message, data = {}) {
if (!application.debugLogging) return;
var logFile = application.logDirectory & "debug_" & dateFormat(now(), "yyyy-mm-dd") & ".log";
var logLine = "[" & timeFormat(now(), "HH:mm:ss") & "] " & message;
if (!structIsEmpty(data)) {
logLine &= " | " & serializeJSON(data);
}
try {
if (!directoryExists(application.logDirectory)) {
directoryCreate(application.logDirectory);
}
fileAppend(logFile, logLine & chr(10));
} catch (any e) {
// Silent fail - don't break app if logging fails
}
}
function apiError(message, detail = "", statusCode = 500) {
var response = {
"OK": false,
"ERROR": "server_error",
"MESSAGE": application.showDetailedErrors ? message : "An error occurred"
};
if (application.showDetailedErrors && len(detail)) {
response["DETAIL"] = detail;
response["STACK"] = "";
}
return response;
}
// ============================================
// PERFORMANCE PROFILING
// ============================================
application.perfEnabled = true;
if (!structKeyExists(application, "perfBuffer")) {
application.perfBuffer = [];
}
/**
* Drop-in replacement for queryExecute() that tracks query count and time.
* Opt-in: use in endpoints where you want accurate DB time breakdown.
*/
function queryTimed(required string sql, any params = [], struct options = {}) {
if (!structKeyExists(request, "_perf_queryCount")) {
request._perf_queryCount = 0;
request._perf_queryTimeMs = 0;
}
var t = getTickCount();
var result = queryExecute(arguments.sql, arguments.params, arguments.options);
var elapsed = getTickCount() - t;
request._perf_queryCount++;
request._perf_queryTimeMs += elapsed;
return result;
}
/**
* Flush the in-memory perf buffer to the ApiPerfLogs MySQL table.
* Thread-safe: duplicates buffer under lock, then inserts outside lock.
*/
function flushPerfBuffer() {
// Auto-create table if it doesn't exist
if (!structKeyExists(application, "_perfTableChecked")) {
try {
queryExecute("
CREATE TABLE IF NOT EXISTS ApiPerfLogs (
ID INT AUTO_INCREMENT PRIMARY KEY,
Endpoint VARCHAR(255) NOT NULL,
TotalMs INT NOT NULL DEFAULT 0,
DbMs INT NOT NULL DEFAULT 0,
AppMs INT NOT NULL DEFAULT 0,
QueryCount INT NOT NULL DEFAULT 0,
ResponseBytes INT NOT NULL DEFAULT 0,
BusinessID INT NOT NULL DEFAULT 0,
UserID INT NOT NULL DEFAULT 0,
LoggedAt DATETIME NOT NULL,
INDEX idx_loggedat (LoggedAt),
INDEX idx_endpoint (Endpoint)
) ENGINE=InnoDB
", {}, { datasource: "payfrit" });
application._perfTableChecked = true;
} catch (any e) {}
}
var batch = [];
lock name="payfrit_perfBuffer" timeout="2" type="exclusive" {
if (structKeyExists(application, "perfBuffer")) {
batch = duplicate(application.perfBuffer);
application.perfBuffer = [];
}
}
if (arrayLen(batch) == 0) return;
try {
var sql = "INSERT INTO ApiPerfLogs (Endpoint, TotalMs, DbMs, AppMs, QueryCount, ResponseBytes, BusinessID, UserID, LoggedAt) VALUES ";
var rows = [];
for (var m in batch) {
arrayAppend(rows,
"('" & replace(m.endpoint, "'", "''", "all") & "',"
& val(m.totalMs) & "," & val(m.dbMs) & "," & val(m.appMs) & ","
& val(m.queryCount) & "," & val(m.responseBytes) & ","
& val(m.businessId) & "," & val(m.userId) & ",'"
& m.loggedAt & "')"
);
}
sql &= arrayToList(rows, ",");
queryExecute(sql, {}, { datasource: "payfrit" });
// Cleanup old data (1% chance per flush)
if (randRange(1, 100) == 1) {
queryExecute(
"DELETE FROM ApiPerfLogs WHERE LoggedAt < DATE_SUB(NOW(), INTERVAL 30 DAY)",
{}, { datasource: "payfrit" }
);
}
} catch (any e) {
// Silent fail - never break the app for profiling
}
}
/**
* Log performance metrics for the current request.
* Called automatically from apiAbort().
*/
function logPerf(numeric responseBytes = 0) {
if (!structKeyExists(application, "perfEnabled") || !application.perfEnabled) return;
if (!structKeyExists(request, "_perf_start")) return;
// Safety valve: don't let buffer grow unbounded if flush is failing
if (structKeyExists(application, "perfBuffer") && arrayLen(application.perfBuffer) > 1000) return;
var totalMs = getTickCount() - request._perf_start;
var dbMs = structKeyExists(request, "_perf_queryTimeMs") ? request._perf_queryTimeMs : 0;
var metric = {
endpoint: structKeyExists(request, "_api_path") ? request._api_path : "unknown",
totalMs: totalMs,
dbMs: dbMs,
appMs: totalMs - dbMs,
queryCount: structKeyExists(request, "_perf_queryCount") ? request._perf_queryCount : 0,
responseBytes: arguments.responseBytes,
businessId: structKeyExists(request, "BusinessID") ? val(request.BusinessID) : 0,
userId: structKeyExists(request, "UserID") ? val(request.UserID) : 0,
loggedAt: dateTimeFormat(now(), "yyyy-mm-dd HH:nn:ss")
};
var shouldFlush = false;
lock name="payfrit_perfBuffer" timeout="1" type="exclusive" {
arrayAppend(application.perfBuffer, metric);
if (arrayLen(application.perfBuffer) >= 100) {
shouldFlush = true;
}
}
if (shouldFlush) {
thread name="perfFlush_#createUUID()#" {
flushPerfBuffer();
}
}
}
// Store in application scope
application.isDevEnvironment = isDevEnvironment;
application.baseUrl = isDevEnvironment ? "https://dev.payfrit.com" : "https://biz.payfrit.com";
application.isDev = isDev;
application.logDebug = logDebug;
application.apiError = apiError;
application.queryTimed = queryTimed;
application.logPerf = logPerf;
application.flushPerfBuffer = flushPerfBuffer;
</cfscript>