/** * Time Travel Endpoint - DEV ONLY * * Allows manipulation of timestamps for testing time-based features. * Examples: token expiry, trial periods, scheduled events. * * POST: { "action": "expireTokens", "userId": 123 } * POST: { "action": "setUserCreated", "userId": 123, "daysAgo": 30 } */ function apiAbort(required struct payload) { writeOutput(serializeJSON(payload)); abort; } // SAFETY: Only allow on dev environment if (!structKeyExists(application, "isDevEnvironment") || !application.isDevEnvironment) { apiAbort({ "OK": false, "ERROR": "forbidden", "MESSAGE": "This endpoint is only available in development" }); } function readJsonBody() { var raw = getHttpRequestData().content; if (isNull(raw)) raw = ""; if (!len(trim(raw))) return {}; try { var data = deserializeJSON(raw); if (isStruct(data)) return data; } catch (any e) {} return {}; } try { if (cgi.request_method != "POST") { apiAbort({ "OK": false, "ERROR": "method_not_allowed", "MESSAGE": "POST required" }); } data = readJsonBody(); action = structKeyExists(data, "action") ? lcase(data.action) : ""; switch (action) { case "expiretokens": // Expire all tokens for a user (simulate session timeout) if (!structKeyExists(data, "userId")) { apiAbort({ "OK": false, "ERROR": "missing_userId" }); } queryExecute(" UPDATE UserTokens SET CreatedAt = DATE_SUB(NOW(), INTERVAL 30 DAY) WHERE UserID = :userId ", { userId: { value: data.userId, cfsqltype: "cf_sql_integer" } }, { datasource: "payfrit" }); writeOutput(serializeJSON({ "OK": true, "message": "Tokens expired for user " & data.userId })); break; case "setusercreated": // Backdate user creation (for testing trial periods, etc) if (!structKeyExists(data, "userId") || !structKeyExists(data, "daysAgo")) { apiAbort({ "OK": false, "ERROR": "missing_userId_or_daysAgo" }); } queryExecute(" UPDATE Users SET AddedOn = DATE_SUB(NOW(), INTERVAL :days DAY) WHERE UserID = :userId ", { userId: { value: data.userId, cfsqltype: "cf_sql_integer" }, days: { value: data.daysAgo, cfsqltype: "cf_sql_integer" } }, { datasource: "payfrit" }); writeOutput(serializeJSON({ "OK": true, "message": "User " & data.userId & " created date set to " & data.daysAgo & " days ago" })); break; case "clearotps": // Clear all OTPs (force re-request) queryExecute(" UPDATE Users SET MobileVerifyCode = '' ", {}, { datasource: "payfrit" }); writeOutput(serializeJSON({ "OK": true, "message": "All OTPs cleared" })); break; case "resetuser": // Reset a user to unverified state (for retesting signup) if (!structKeyExists(data, "phone")) { apiAbort({ "OK": false, "ERROR": "missing_phone" }); } queryExecute(" UPDATE Users SET IsContactVerified = 0, IsActive = 0, FirstName = NULL, LastName = NULL, MobileVerifyCode = '123456' WHERE ContactNumber = :phone ", { phone: { value: data.phone, cfsqltype: "cf_sql_varchar" } }, { datasource: "payfrit" }); writeOutput(serializeJSON({ "OK": true, "message": "User with phone " & data.phone & " reset to unverified" })); break; default: apiAbort({ "OK": false, "ERROR": "unknown_action", "MESSAGE": "Valid actions: expireTokens, setUserCreated, clearOTPs, resetUser" }); } } catch (any e) { apiAbort({ "OK": false, "ERROR": "server_error", "MESSAGE": application.showDetailedErrors ? e.message : "An error occurred" }); }