- Add avatar.cfm: GET/POST for user profile photos with multi-extension support - Add profile.cfm: GET/POST for user profile (name, email, phone) - Add history.cfm: Order history endpoint with pagination - Add addresses/list.cfm and add.cfm: Delivery address management - Add setOrderType.cfm: Set delivery/takeaway type on orders - Add checkToken.cfm: Debug endpoint for token validation - Fix headerValue() in Application.cfm to use servlet request object (Lucee CGI scope doesn't expose custom HTTP headers like X-User-Token) - Update public allowlist for new endpoints - Add privacy.html page 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
182 lines
5.2 KiB
Text
182 lines
5.2 KiB
Text
<cfsetting showdebugoutput="false">
|
|
<cfsetting enablecfoutputonly="true">
|
|
<cfcontent type="application/json; charset=utf-8" reset="true">
|
|
<cfheader name="Cache-Control" value="no-store">
|
|
|
|
<!---
|
|
Avatar Upload/Get API
|
|
|
|
GET: Returns avatar URL for authenticated user
|
|
POST: Uploads new avatar image (multipart form data)
|
|
|
|
Stores images as: /uploads/users/{UserID}.jpg
|
|
--->
|
|
|
|
<cfscript>
|
|
function apiAbort(required struct payload) {
|
|
writeOutput(serializeJSON(payload));
|
|
abort;
|
|
}
|
|
|
|
// Helper to get header value - use servlet request object (CGI scope doesn't expose custom HTTP headers in Lucee)
|
|
function getHeader(name) {
|
|
try {
|
|
req = getPageContext().getRequest();
|
|
val = req.getHeader(arguments.name);
|
|
if (!isNull(val)) return trim(val);
|
|
} catch (any e) {
|
|
// Fall back to CGI scope
|
|
k = "HTTP_" & ucase(reReplace(arguments.name, "[^A-Za-z0-9]", "_", "all"));
|
|
if (structKeyExists(cgi, k)) return trim(cgi[k]);
|
|
}
|
|
return "";
|
|
}
|
|
|
|
// Get authenticated user ID - try request scope first, then do our own token lookup
|
|
userId = 0;
|
|
debugInfo = {
|
|
"requestUserID": structKeyExists(request, "UserID") ? request.UserID : "not_set",
|
|
"headerToken": "",
|
|
"tokenLookupResult": "not_attempted"
|
|
};
|
|
|
|
if (structKeyExists(request, "UserID") && isNumeric(request.UserID) && request.UserID > 0) {
|
|
userId = request.UserID;
|
|
debugInfo.source = "request_scope";
|
|
} else {
|
|
// Do our own token lookup for multipart requests
|
|
userToken = getHeader("X-User-Token");
|
|
debugInfo.headerToken = len(userToken) ? left(userToken, 8) & "..." : "empty";
|
|
|
|
if (len(userToken)) {
|
|
try {
|
|
qTok = queryExecute(
|
|
"SELECT UserID FROM UserTokens WHERE Token = ? LIMIT 1",
|
|
[ { value = userToken, cfsqltype = "cf_sql_varchar" } ],
|
|
{ datasource = "payfrit" }
|
|
);
|
|
debugInfo.tokenLookupResult = "found_#qTok.recordCount#_records";
|
|
if (qTok.recordCount EQ 1) {
|
|
userId = qTok.UserID;
|
|
debugInfo.source = "token_lookup";
|
|
}
|
|
} catch (any e) {
|
|
debugInfo.tokenLookupResult = "error: " & e.message;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (userId <= 0) {
|
|
apiAbort({ "OK": false, "ERROR": "not_logged_in", "MESSAGE": "Authentication required", "DEBUG": debugInfo });
|
|
}
|
|
|
|
// Use absolute path from web root
|
|
uploadsPath = expandPath("/uploads/users/");
|
|
|
|
// Check for avatar with various extensions (case-insensitive)
|
|
function findAvatarFile(basePath, userId) {
|
|
extensions = ["jpg", "jpeg", "png", "gif", "webp", "JPG", "JPEG", "PNG", "GIF", "WEBP"];
|
|
for (ext in extensions) {
|
|
testPath = basePath & userId & "." & ext;
|
|
if (fileExists(testPath)) {
|
|
return { "exists": true, "path": testPath, "filename": userId & "." & ext };
|
|
}
|
|
}
|
|
return { "exists": false, "path": basePath & userId & ".jpg", "filename": userId & ".jpg" };
|
|
}
|
|
|
|
avatarInfo = findAvatarFile(uploadsPath, userId);
|
|
avatarPath = avatarInfo.path;
|
|
avatarFilename = avatarInfo.filename;
|
|
avatarUrl = "https://biz.payfrit.com/uploads/users/" & avatarFilename;
|
|
|
|
// Handle GET - return current avatar URL
|
|
if (cgi.REQUEST_METHOD == "GET") {
|
|
hasAvatar = avatarInfo.exists;
|
|
|
|
writeOutput(serializeJSON({
|
|
"OK": true,
|
|
"HAS_AVATAR": hasAvatar,
|
|
"AVATAR_URL": hasAvatar ? (avatarUrl & "?t=" & getTickCount()) : ""
|
|
}));
|
|
abort;
|
|
}
|
|
|
|
// Handle POST - upload new avatar
|
|
if (cgi.REQUEST_METHOD == "POST") {
|
|
try {
|
|
// Check if file was uploaded
|
|
if (!structKeyExists(form, "avatar") || !len(form.avatar)) {
|
|
apiAbort({ "OK": false, "ERROR": "missing_file", "MESSAGE": "No avatar file provided" });
|
|
}
|
|
|
|
// Get uploaded file info
|
|
uploadedFile = form.avatar;
|
|
|
|
// Ensure uploads directory exists
|
|
if (!directoryExists(uploadsPath)) {
|
|
directoryCreate(uploadsPath);
|
|
}
|
|
|
|
// Process the upload
|
|
uploadResult = fileUpload(
|
|
destination = uploadsPath,
|
|
fileField = "avatar",
|
|
nameConflict = "overwrite",
|
|
accept = "image/jpeg,image/png,image/gif,image/webp"
|
|
);
|
|
|
|
// Rename to userId.jpg (convert if needed)
|
|
uploadedPath = uploadsPath & uploadResult.serverFile;
|
|
|
|
// Read the image and save as JPEG
|
|
try {
|
|
img = imageRead(uploadedPath);
|
|
|
|
// Resize if too large (max 500x500 for avatars)
|
|
if (img.width > 500 || img.height > 500) {
|
|
if (img.width > img.height) {
|
|
imageScaleToFit(img, 500, "");
|
|
} else {
|
|
imageScaleToFit(img, "", 500);
|
|
}
|
|
}
|
|
|
|
// Save as JPEG
|
|
imageWrite(img, avatarPath, 0.85);
|
|
|
|
// Delete original if different from target
|
|
if (uploadedPath != avatarPath && fileExists(uploadedPath)) {
|
|
fileDelete(uploadedPath);
|
|
}
|
|
|
|
} catch (any imgErr) {
|
|
// If image processing fails, just rename the file
|
|
if (uploadedPath != avatarPath) {
|
|
if (fileExists(avatarPath)) {
|
|
fileDelete(avatarPath);
|
|
}
|
|
fileMove(uploadedPath, avatarPath);
|
|
}
|
|
}
|
|
|
|
writeOutput(serializeJSON({
|
|
"OK": true,
|
|
"MESSAGE": "Avatar uploaded successfully",
|
|
"AVATAR_URL": avatarUrl & "?t=" & getTickCount()
|
|
}));
|
|
abort;
|
|
|
|
} catch (any e) {
|
|
apiAbort({
|
|
"OK": false,
|
|
"ERROR": "upload_error",
|
|
"MESSAGE": "Failed to upload avatar",
|
|
"DETAIL": e.message
|
|
});
|
|
}
|
|
}
|
|
|
|
// Unknown method
|
|
apiAbort({ "OK": false, "ERROR": "bad_method", "MESSAGE": "Use GET or POST" });
|
|
</cfscript>
|