#include "UserController.h" #include "../services/DatabaseService.h" #include #include #include #include #include #include #include using namespace drogon::orm; namespace { HttpResponsePtr jsonResp(const Json::Value& j, HttpStatusCode c = k200OK) { auto r = HttpResponse::newHttpJsonResponse(j); r->setStatusCode(c); return r; } HttpResponsePtr jsonError(const std::string& error, HttpStatusCode code = k400BadRequest) { Json::Value j; j["success"] = false; j["error"] = error; return jsonResp(j, code); } std::string generateRandomFilename(const std::string& extension) { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(0, 255); std::stringstream ss; for (int i = 0; i < 16; ++i) { ss << std::hex << std::setw(2) << std::setfill('0') << dis(gen); } return ss.str() + "." + extension; } bool ensureDirectoryExists(const std::string& path) { try { std::filesystem::create_directories(path); // Set permissions to 755 std::filesystem::permissions(path, std::filesystem::perms::owner_all | std::filesystem::perms::group_read | std::filesystem::perms::group_exec | std::filesystem::perms::others_read | std::filesystem::perms::others_exec ); return true; } catch (const std::exception& e) { LOG_ERROR << "Failed to create directory " << path << ": " << e.what(); return false; } } // Helper to set httpOnly auth cookie void setAuthCookie(const HttpResponsePtr& resp, const std::string& token) { Cookie authCookie("auth_token", token); authCookie.setPath("/"); authCookie.setHttpOnly(true); authCookie.setSecure(false); // Set to true in production with HTTPS authCookie.setMaxAge(86400); // 24 hours authCookie.setSameSite(Cookie::SameSite::kLax); resp->addCookie(authCookie); } // Helper to clear auth cookie void clearAuthCookie(const HttpResponsePtr& resp) { Cookie authCookie("auth_token", ""); authCookie.setPath("/"); authCookie.setHttpOnly(true); authCookie.setMaxAge(0); // Expire immediately resp->addCookie(authCookie); } } UserInfo UserController::getUserFromRequest(const HttpRequestPtr &req) { UserInfo user; // First try to get from cookie std::string token = req->getCookie("auth_token"); // Fallback to Authorization header for API clients if (token.empty()) { std::string auth = req->getHeader("Authorization"); if (!auth.empty() && auth.substr(0, 7) == "Bearer ") { token = auth.substr(7); } } if (!token.empty()) { AuthService::getInstance().validateToken(token, user); } return user; } void UserController::register_(const HttpRequestPtr &req, std::function &&callback) { try { LOG_DEBUG << "Registration request received"; auto json = req->getJsonObject(); if (!json) { LOG_WARN << "Invalid JSON in registration request"; callback(jsonError("Invalid JSON")); return; } // Check if all required fields exist before accessing them if (!(*json).isMember("username") || !(*json).isMember("password") || !(*json).isMember("publicKey") || !(*json).isMember("fingerprint")) { LOG_WARN << "Missing required fields in registration request"; callback(jsonError("Missing required fields")); return; } // Safely extract the values std::string username = (*json)["username"].asString(); std::string password = (*json)["password"].asString(); std::string publicKey = (*json)["publicKey"].asString(); std::string fingerprint = (*json)["fingerprint"].asString(); // Validate that none of the strings are empty if (username.empty() || password.empty() || publicKey.empty() || fingerprint.empty()) { LOG_WARN << "Empty required fields in registration request"; callback(jsonError("All fields are required")); return; } // Additional validation if (username.length() > 30) { callback(jsonError("Username too long (max 30 characters)")); return; } if (password.length() < 8) { callback(jsonError("Password must be at least 8 characters")); return; } LOG_INFO << "Processing registration for user: " << username; AuthService::getInstance().registerUser(username, password, publicKey, fingerprint, [callback, username](bool success, const std::string& error, int64_t userId) { if (success) { LOG_INFO << "User registered successfully: " << username << " (ID: " << userId << ")"; Json::Value resp; resp["success"] = true; resp["userId"] = static_cast(userId); callback(jsonResp(resp)); } else { LOG_WARN << "Registration failed for " << username << ": " << error; callback(jsonError(error)); } }); } catch (const std::exception& e) { LOG_ERROR << "Exception in register_: " << e.what(); callback(jsonError("Internal server error")); } catch (...) { LOG_ERROR << "Unknown exception in register_"; callback(jsonError("Internal server error")); } } void UserController::login(const HttpRequestPtr &req, std::function &&callback) { try { LOG_DEBUG << "Login request received"; auto json = req->getJsonObject(); if (!json) { callback(jsonError("Invalid JSON")); return; } // Check if fields exist before accessing if (!(*json).isMember("username") || !(*json).isMember("password")) { callback(jsonError("Missing credentials")); return; } std::string username = (*json)["username"].asString(); std::string password = (*json)["password"].asString(); if (username.empty() || password.empty()) { callback(jsonError("Missing credentials")); return; } LOG_INFO << "Login attempt for user: " << username; AuthService::getInstance().loginUser(username, password, [callback, username](bool success, const std::string& token, const UserInfo& user) { if (success) { LOG_INFO << "Login successful for user: " << username; Json::Value resp; resp["success"] = true; // Don't send token in body for cookie-based auth resp["user"]["id"] = static_cast(user.id); resp["user"]["username"] = user.username; resp["user"]["isAdmin"] = user.isAdmin; resp["user"]["isStreamer"] = user.isStreamer; resp["user"]["isPgpOnly"] = user.isPgpOnly; resp["user"]["bio"] = user.bio; resp["user"]["avatarUrl"] = user.avatarUrl; resp["user"]["pgpOnlyEnabledAt"] = user.pgpOnlyEnabledAt; resp["user"]["colorCode"] = user.colorCode; auto response = jsonResp(resp); setAuthCookie(response, token); callback(response); } else { LOG_WARN << "Login failed for user: " << username; callback(jsonError(token.empty() ? "Invalid credentials" : token, k401Unauthorized)); } }); } catch (const std::exception& e) { LOG_ERROR << "Exception in login: " << e.what(); callback(jsonError("Internal server error")); } catch (...) { LOG_ERROR << "Unknown exception in login"; callback(jsonError("Internal server error")); } } void UserController::logout(const HttpRequestPtr &req, std::function &&callback) { try { Json::Value resp; resp["success"] = true; resp["message"] = "Logged out successfully"; auto response = jsonResp(resp); clearAuthCookie(response); callback(response); } catch (const std::exception& e) { LOG_ERROR << "Exception in logout: " << e.what(); callback(jsonError("Internal server error")); } } void UserController::pgpChallenge(const HttpRequestPtr &req, std::function &&callback) { try { auto json = req->getJsonObject(); if (!json) { callback(jsonError("Invalid JSON")); return; } if (!(*json).isMember("username")) { callback(jsonError("Username required")); return; } std::string username = (*json)["username"].asString(); if (username.empty()) { callback(jsonError("Username required")); return; } AuthService::getInstance().initiatePgpLogin(username, [callback](bool success, const std::string& challenge, const std::string& publicKey) { if (success) { Json::Value resp; resp["success"] = true; resp["challenge"] = challenge; resp["publicKey"] = publicKey; callback(jsonResp(resp)); } else { callback(jsonError("User not found or PGP not enabled", k404NotFound)); } }); } catch (const std::exception& e) { LOG_ERROR << "Exception in pgpChallenge: " << e.what(); callback(jsonError("Internal server error")); } } void UserController::pgpVerify(const HttpRequestPtr &req, std::function &&callback) { try { auto json = req->getJsonObject(); if (!json) { callback(jsonError("Invalid JSON")); return; } if (!(*json).isMember("username") || !(*json).isMember("signature") || !(*json).isMember("challenge")) { callback(jsonError("Missing required fields")); return; } std::string username = (*json)["username"].asString(); std::string signature = (*json)["signature"].asString(); std::string challenge = (*json)["challenge"].asString(); if (username.empty() || signature.empty() || challenge.empty()) { callback(jsonError("Missing required fields")); return; } AuthService::getInstance().verifyPgpLogin(username, signature, challenge, [callback](bool success, const std::string& token, const UserInfo& user) { if (success) { Json::Value resp; resp["success"] = true; // Don't send token in body for cookie-based auth resp["user"]["id"] = static_cast(user.id); resp["user"]["username"] = user.username; resp["user"]["isAdmin"] = user.isAdmin; resp["user"]["isStreamer"] = user.isStreamer; resp["user"]["isPgpOnly"] = user.isPgpOnly; resp["user"]["bio"] = user.bio; resp["user"]["avatarUrl"] = user.avatarUrl; resp["user"]["pgpOnlyEnabledAt"] = user.pgpOnlyEnabledAt; resp["user"]["colorCode"] = user.colorCode; auto response = jsonResp(resp); setAuthCookie(response, token); callback(response); } else { callback(jsonError("Invalid signature", k401Unauthorized)); } }); } catch (const std::exception& e) { LOG_ERROR << "Exception in pgpVerify: " << e.what(); callback(jsonError("Internal server error")); } } void UserController::getCurrentUser(const HttpRequestPtr &req, std::function &&callback) { try { UserInfo user = getUserFromRequest(req); if (user.id == 0) { callback(jsonError("Unauthorized", k401Unauthorized)); return; } auto dbClient = app().getDbClient(); *dbClient << "SELECT id, username, is_admin, is_streamer, is_pgp_only, bio, avatar_url, pgp_only_enabled_at, user_color " "FROM users WHERE id = $1" << user.id >> [callback](const Result& r) { if (r.empty()) { callback(jsonError("User not found", k404NotFound)); return; } Json::Value resp; resp["success"] = true; resp["user"]["id"] = static_cast(r[0]["id"].as()); resp["user"]["username"] = r[0]["username"].as(); resp["user"]["isAdmin"] = r[0]["is_admin"].isNull() ? false : r[0]["is_admin"].as(); resp["user"]["isStreamer"] = r[0]["is_streamer"].isNull() ? false : r[0]["is_streamer"].as(); resp["user"]["isPgpOnly"] = r[0]["is_pgp_only"].isNull() ? false : r[0]["is_pgp_only"].as(); resp["user"]["bio"] = r[0]["bio"].isNull() ? "" : r[0]["bio"].as(); resp["user"]["avatarUrl"] = r[0]["avatar_url"].isNull() ? "" : r[0]["avatar_url"].as(); resp["user"]["pgpOnlyEnabledAt"] = r[0]["pgp_only_enabled_at"].isNull() ? "" : r[0]["pgp_only_enabled_at"].as(); resp["user"]["colorCode"] = r[0]["user_color"].isNull() ? "#561D5E" : r[0]["user_color"].as(); callback(jsonResp(resp)); } >> [callback](const DrogonDbException& e) { LOG_ERROR << "Failed to get user data: " << e.base().what(); callback(jsonError("Database error")); }; } catch (const std::exception& e) { LOG_ERROR << "Exception in getCurrentUser: " << e.what(); callback(jsonError("Internal server error")); } } void UserController::updateProfile(const HttpRequestPtr &req, std::function &&callback) { try { UserInfo user = getUserFromRequest(req); if (user.id == 0) { callback(jsonError("Unauthorized", k401Unauthorized)); return; } auto json = req->getJsonObject(); if (!json) { callback(jsonError("Invalid JSON")); return; } std::string bio = (*json).isMember("bio") ? (*json)["bio"].asString() : ""; auto dbClient = app().getDbClient(); *dbClient << "UPDATE users SET bio = $1 WHERE id = $2" << bio << user.id >> [callback](const Result&) { Json::Value resp; resp["success"] = true; resp["message"] = "Profile updated successfully"; callback(jsonResp(resp)); } >> [callback](const DrogonDbException& e) { LOG_ERROR << "Failed to update profile: " << e.base().what(); callback(jsonError("Failed to update profile")); }; } catch (const std::exception& e) { LOG_ERROR << "Exception in updateProfile: " << e.what(); callback(jsonError("Internal server error")); } } void UserController::updatePassword(const HttpRequestPtr &req, std::function &&callback) { try { UserInfo user = getUserFromRequest(req); if (user.id == 0) { callback(jsonError("Unauthorized", k401Unauthorized)); return; } auto json = req->getJsonObject(); if (!json) { callback(jsonError("Invalid JSON")); return; } if (!(*json).isMember("oldPassword") || !(*json).isMember("newPassword")) { callback(jsonError("Missing passwords")); return; } std::string oldPassword = (*json)["oldPassword"].asString(); std::string newPassword = (*json)["newPassword"].asString(); if (oldPassword.empty() || newPassword.empty()) { callback(jsonError("Missing passwords")); return; } AuthService::getInstance().updatePassword(user.id, oldPassword, newPassword, [callback](bool success, const std::string& error) { if (success) { Json::Value resp; resp["success"] = true; callback(jsonResp(resp)); } else { callback(jsonError(error)); } }); } catch (const std::exception& e) { LOG_ERROR << "Exception in updatePassword: " << e.what(); callback(jsonError("Internal server error")); } } void UserController::togglePgpOnly(const HttpRequestPtr &req, std::function &&callback) { try { UserInfo user = getUserFromRequest(req); if (user.id == 0) { callback(jsonError("Unauthorized", k401Unauthorized)); return; } auto json = req->getJsonObject(); if (!json) { callback(jsonError("Invalid JSON")); return; } bool enable = (*json).isMember("enable") ? (*json)["enable"].asBool() : false; auto dbClient = app().getDbClient(); *dbClient << "UPDATE users SET is_pgp_only = $1 WHERE id = $2 RETURNING pgp_only_enabled_at" << enable << user.id >> [callback, enable](const Result& r) { Json::Value resp; resp["success"] = true; resp["pgpOnly"] = enable; // Return the timestamp if it was just enabled if (enable && !r.empty() && !r[0]["pgp_only_enabled_at"].isNull()) { resp["pgpOnlyEnabledAt"] = r[0]["pgp_only_enabled_at"].as(); } callback(jsonResp(resp)); } >> [callback](const DrogonDbException& e) { LOG_ERROR << "Failed to update PGP setting: " << e.base().what(); callback(jsonError("Failed to update setting")); }; } catch (const std::exception& e) { LOG_ERROR << "Exception in togglePgpOnly: " << e.what(); callback(jsonError("Internal server error")); } } void UserController::addPgpKey(const HttpRequestPtr &req, std::function &&callback) { try { UserInfo user = getUserFromRequest(req); if (user.id == 0) { callback(jsonError("Unauthorized", k401Unauthorized)); return; } auto json = req->getJsonObject(); if (!json) { callback(jsonError("Invalid JSON")); return; } if (!(*json).isMember("publicKey") || !(*json).isMember("fingerprint")) { callback(jsonError("Missing key data")); return; } std::string publicKey = (*json)["publicKey"].asString(); std::string fingerprint = (*json)["fingerprint"].asString(); if (publicKey.empty() || fingerprint.empty()) { callback(jsonError("Missing key data")); return; } auto dbClient = app().getDbClient(); // Check if fingerprint already exists *dbClient << "SELECT id FROM pgp_keys WHERE fingerprint = $1" << fingerprint >> [dbClient, user, publicKey, fingerprint, callback](const Result& r) { if (!r.empty()) { callback(jsonError("This PGP key is already registered")); return; } *dbClient << "INSERT INTO pgp_keys (user_id, public_key, fingerprint) VALUES ($1, $2, $3)" << user.id << publicKey << fingerprint >> [callback](const Result&) { Json::Value resp; resp["success"] = true; callback(jsonResp(resp)); } >> [callback](const DrogonDbException& e) { LOG_ERROR << "Failed to add PGP key: " << e.base().what(); callback(jsonError("Failed to add PGP key")); }; } >> [callback](const DrogonDbException& e) { LOG_ERROR << "Database error: " << e.base().what(); callback(jsonError("Database error")); }; } catch (const std::exception& e) { LOG_ERROR << "Exception in addPgpKey: " << e.what(); callback(jsonError("Internal server error")); } } void UserController::getPgpKeys(const HttpRequestPtr &req, std::function &&callback) { try { UserInfo user = getUserFromRequest(req); if (user.id == 0) { callback(jsonError("Unauthorized", k401Unauthorized)); return; } auto dbClient = app().getDbClient(); *dbClient << "SELECT public_key, fingerprint, created_at FROM pgp_keys " "WHERE user_id = $1 ORDER BY created_at DESC" << user.id >> [callback](const Result& r) { Json::Value resp; resp["success"] = true; Json::Value keys(Json::arrayValue); for (const auto& row : r) { Json::Value key; key["publicKey"] = row["public_key"].as(); key["fingerprint"] = row["fingerprint"].as(); key["createdAt"] = row["created_at"].as(); keys.append(key); } resp["keys"] = keys; callback(jsonResp(resp)); } >> [callback](const DrogonDbException& e) { LOG_ERROR << "Failed to get PGP keys: " << e.base().what(); callback(jsonError("Failed to get PGP keys")); }; } catch (const std::exception& e) { LOG_ERROR << "Exception in getPgpKeys: " << e.what(); callback(jsonError("Internal server error")); } } void UserController::uploadAvatar(const HttpRequestPtr &req, std::function &&callback) { try { UserInfo user = getUserFromRequest(req); if (user.id == 0) { callback(jsonError("Unauthorized", k401Unauthorized)); return; } MultiPartParser parser; parser.parse(req); if (parser.getFiles().empty()) { callback(jsonError("No file uploaded")); return; } const auto& file = parser.getFiles()[0]; // Validate file size (250KB max) if (file.fileLength() > 250 * 1024) { callback(jsonError("File too large (max 250KB)")); return; } // Validate file type std::string ext = std::string(file.getFileExtension()); if (ext != "jpg" && ext != "jpeg" && ext != "png" && ext != "gif") { callback(jsonError("Invalid file type (jpg, png, gif only)")); return; } // Ensure uploads directory exists const std::string uploadDir = "/app/uploads/avatars"; if (!ensureDirectoryExists(uploadDir)) { callback(jsonError("Failed to create upload directory")); return; } // Generate unique filename using hex string std::string filename = generateRandomFilename(ext); // Build the full file path std::string fullPath = uploadDir + "/" + filename; // Ensure the file doesn't already exist (extremely unlikely with random names) if (std::filesystem::exists(fullPath)) { LOG_WARN << "File already exists, regenerating name"; filename = generateRandomFilename(ext); fullPath = uploadDir + "/" + filename; } try { // Get the uploaded file data and size const char* fileData = file.fileData(); size_t fileSize = file.fileLength(); if (!fileData || fileSize == 0) { LOG_ERROR << "Empty file data"; callback(jsonError("Empty file uploaded")); return; } // Write file data directly to avoid directory creation issues std::ofstream ofs(fullPath, std::ios::binary); if (!ofs) { LOG_ERROR << "Failed to open file for writing: " << fullPath; callback(jsonError("Failed to create file")); return; } ofs.write(fileData, fileSize); ofs.close(); if (!ofs) { LOG_ERROR << "Failed to write file data"; callback(jsonError("Failed to write file")); return; } // Verify it's actually a file if (!std::filesystem::is_regular_file(fullPath)) { LOG_ERROR << "Created path is not a regular file: " << fullPath; std::filesystem::remove_all(fullPath); // Clean up callback(jsonError("Failed to save avatar correctly")); return; } // Set file permissions to 644 std::filesystem::permissions(fullPath, std::filesystem::perms::owner_read | std::filesystem::perms::owner_write | std::filesystem::perms::group_read | std::filesystem::perms::others_read ); LOG_INFO << "Avatar saved successfully to: " << fullPath; LOG_INFO << "File size: " << std::filesystem::file_size(fullPath) << " bytes"; } catch (const std::exception& e) { LOG_ERROR << "Exception while saving avatar: " << e.what(); // Clean up any partial files/directories if (std::filesystem::exists(fullPath)) { std::filesystem::remove_all(fullPath); } callback(jsonError("Failed to save avatar")); return; } // Store as proper URL path std::string avatarUrl = "/uploads/avatars/" + filename; // Update database with the URL auto dbClient = app().getDbClient(); *dbClient << "UPDATE users SET avatar_url = $1 WHERE id = $2" << avatarUrl << user.id >> [callback, avatarUrl](const Result&) { Json::Value resp; resp["success"] = true; resp["avatarUrl"] = avatarUrl; callback(jsonResp(resp)); } >> [callback](const DrogonDbException& e) { LOG_ERROR << "Failed to update avatar: " << e.base().what(); callback(jsonError("Failed to update avatar")); }; } catch (const std::exception& e) { LOG_ERROR << "Exception in uploadAvatar: " << e.what(); callback(jsonError("Internal server error")); } } void UserController::getProfile(const HttpRequestPtr &, std::function &&callback, const std::string &username) { try { auto dbClient = app().getDbClient(); *dbClient << "SELECT u.username, u.bio, u.avatar_url, u.created_at, " "u.is_pgp_only, u.pgp_only_enabled_at, u.user_color " "FROM users u WHERE u.username = $1" << username >> [callback](const Result& r) { if (r.empty()) { callback(jsonError("User not found", k404NotFound)); return; } Json::Value resp; resp["success"] = true; resp["profile"]["username"] = r[0]["username"].as(); resp["profile"]["bio"] = r[0]["bio"].isNull() ? "" : r[0]["bio"].as(); resp["profile"]["avatarUrl"] = r[0]["avatar_url"].isNull() ? "" : r[0]["avatar_url"].as(); resp["profile"]["createdAt"] = r[0]["created_at"].as(); resp["profile"]["isPgpOnly"] = r[0]["is_pgp_only"].isNull() ? false : r[0]["is_pgp_only"].as(); resp["profile"]["pgpOnlyEnabledAt"] = r[0]["pgp_only_enabled_at"].isNull() ? "" : r[0]["pgp_only_enabled_at"].as(); resp["profile"]["colorCode"] = r[0]["user_color"].isNull() ? "#561D5E" : r[0]["user_color"].as(); callback(jsonResp(resp)); } >> [callback](const DrogonDbException& e) { LOG_ERROR << "Database error: " << e.base().what(); callback(jsonError("Database error")); }; } catch (const std::exception& e) { LOG_ERROR << "Exception in getProfile: " << e.what(); callback(jsonError("Internal server error")); } } void UserController::getUserPgpKeys(const HttpRequestPtr &, std::function &&callback, const std::string &username) { try { // Public endpoint - no authentication required auto dbClient = app().getDbClient(); *dbClient << "SELECT pk.public_key, pk.fingerprint, pk.created_at " "FROM pgp_keys pk JOIN users u ON pk.user_id = u.id " "WHERE u.username = $1 ORDER BY pk.created_at DESC" << username >> [callback](const Result& r) { Json::Value resp; resp["success"] = true; Json::Value keys(Json::arrayValue); for (const auto& row : r) { Json::Value key; key["publicKey"] = row["public_key"].as(); key["fingerprint"] = row["fingerprint"].as(); key["createdAt"] = row["created_at"].as(); keys.append(key); } resp["keys"] = keys; callback(jsonResp(resp)); } >> [callback](const DrogonDbException& e) { LOG_ERROR << "Failed to get user PGP keys: " << e.base().what(); callback(jsonError("Failed to get PGP keys")); }; } catch (const std::exception& e) { LOG_ERROR << "Exception in getUserPgpKeys: " << e.what(); callback(jsonError("Internal server error")); } } void UserController::updateColor(const HttpRequestPtr &req, std::function &&callback) { try { UserInfo user = getUserFromRequest(req); if (user.id == 0) { callback(jsonError("Unauthorized", k401Unauthorized)); return; } auto json = req->getJsonObject(); if (!json) { callback(jsonError("Invalid JSON")); return; } if (!(*json).isMember("color")) { callback(jsonError("Color is required")); return; } std::string newColor = (*json)["color"].asString(); if (newColor.empty()) { callback(jsonError("Color is required")); return; } AuthService::getInstance().updateUserColor(user.id, newColor, [callback, user](bool success, const std::string& error, const std::string& finalColor) { if (success) { // Fetch updated user info AuthService::getInstance().fetchUserInfo(user.id, [callback, finalColor](bool fetchSuccess, const UserInfo& updatedUser) { if (fetchSuccess) { // Generate new token with updated user info including color std::string newToken = AuthService::getInstance().generateToken(updatedUser); Json::Value resp; resp["success"] = true; resp["color"] = finalColor; resp["user"]["id"] = static_cast(updatedUser.id); resp["user"]["username"] = updatedUser.username; resp["user"]["isAdmin"] = updatedUser.isAdmin; resp["user"]["isStreamer"] = updatedUser.isStreamer; resp["user"]["colorCode"] = updatedUser.colorCode; auto response = jsonResp(resp); // Update auth cookie with new token setAuthCookie(response, newToken); callback(response); } else { // Color was updated but couldn't fetch full user info Json::Value resp; resp["success"] = true; resp["color"] = finalColor; resp["message"] = "Color updated but please refresh for new token"; callback(jsonResp(resp)); } }); } else { callback(jsonError(error)); } }); } catch (const std::exception& e) { LOG_ERROR << "Exception in updateColor: " << e.what(); callback(jsonError("Internal server error")); } } void UserController::getAvailableColors(const HttpRequestPtr &, std::function &&callback) { try { // Define available colors for user profiles Json::Value resp; resp["success"] = true; Json::Value colors(Json::arrayValue); // Add predefined color options colors.append("#561D5E"); // Default purple colors.append("#1E88E5"); // Blue colors.append("#43A047"); // Green colors.append("#E53935"); // Red colors.append("#FB8C00"); // Orange colors.append("#8E24AA"); // Purple variant colors.append("#00ACC1"); // Cyan colors.append("#FFB300"); // Amber colors.append("#546E7A"); // Blue Grey colors.append("#D81B60"); // Pink resp["colors"] = colors; callback(jsonResp(resp)); } catch (const std::exception& e) { LOG_ERROR << "Exception in getAvailableColors: " << e.what(); callback(jsonError("Internal server error")); } }